TLS: Connections refactoring and TLS support.

* Introduce a connection abstraction layer for all socket operations and
integrate it across the code base.
* Provide an optional TLS connections implementation based on OpenSSL.
* Pull a newer version of hiredis with TLS support.
* Tests, redis-cli updates for TLS support.
This commit is contained in:
Yossi Gottlieb 2019-09-12 10:56:54 +03:00
parent e645c794cf
commit 10ffeb03e4
85 changed files with 4625 additions and 835 deletions

133
TLS.md Normal file
View File

@ -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

6
deps/Makefile vendored
View File

@ -41,9 +41,13 @@ distclean:
.PHONY: distclean .PHONY: distclean
ifeq ($(BUILD_TLS),yes)
HIREDIS_MAKE_FLAGS = USE_SSL=1
endif
hiredis: .make-prerequisites hiredis: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd hiredis && $(MAKE) static cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS)
.PHONY: hiredis .PHONY: hiredis

View File

@ -5,3 +5,4 @@
/*.dylib /*.dylib
/*.a /*.a
/*.pc /*.pc
*.dSYM

View File

@ -26,20 +26,72 @@ addons:
- libc6-dev-i386 - libc6-dev-i386
- libc6-dbg:i386 - libc6-dbg:i386
- gcc-multilib - gcc-multilib
- g++-multilib
- valgrind - valgrind
env: env:
- CFLAGS="-Werror" - BITS="32"
- PRE="valgrind --track-origins=yes --leak-check=full" - BITS="64"
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
- TARGET="32bit" TARGET_VARS="32bit-vars" 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
matrix: matrix:
exclude: include:
- os: osx # Windows MinGW cross compile on Linux
env: PRE="valgrind --track-origins=yes --leak-check=full" - 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
- os: osx # Windows MSVC 2017
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" - os: windows
compiler: msvc
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example 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

View File

@ -12,6 +12,16 @@
compare to other values, casting might be necessary or can be removed, if compare to other values, casting might be necessary or can be removed, if
casting was applied before. 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) ### 0.14.0 (2018-09-25)
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) * 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 * Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
* Fix warnings, when compiled with -Wshadow * Fix warnings, when compiled with -Wshadow
* Make hiredis compile in Cygwin on Windows, now CI-tested * Make hiredis compile in Cygwin on Windows, now CI-tested
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
**BREAKING CHANGES**: 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 * Remove backwards compatibility macro's

90
deps/hiredis/CMakeLists.txt vendored Normal file
View File

@ -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)

106
deps/hiredis/Makefile vendored
View File

@ -3,11 +3,17 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com> # Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file # 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 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 TESTS=hiredis-test
LIBNAME=libhiredis LIBNAME=libhiredis
SSL_LIBNAME=libhiredis_ssl
PKGCONFNAME=hiredis.pc PKGCONFNAME=hiredis.pc
SSL_PKGCONFNAME=hiredis_ssl.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR 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') 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++') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
OPTIMIZATION?=-O3 OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
DEBUG_FLAGS?= -g -ggdb DEBUG_FLAGS?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
REAL_LDFLAGS=$(LDFLAGS) REAL_LDFLAGS=$(LDFLAGS)
@ -49,12 +55,30 @@ STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) 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) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=ar rcs $(STLIBNAME) SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=$(AR) rcs
# Platform-specific overrides # Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 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) ifeq ($(uname_S),SunOS)
REAL_LDFLAGS+= -ldl -lnsl -lsocket REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
@ -66,40 +90,61 @@ ifeq ($(uname_S),Darwin)
endif endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) 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) # 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 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 dict.o: dict.c fmacros.h dict.h
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.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 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 read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c 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 test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ) $(DYLIBNAME): $(OBJ)
$(DYLIB_MAKE_CMD) $(OBJ) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
$(STLIBNAME): $(OBJ) $(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) dynamic: $(DYLIBNAME)
static: $(STLIBNAME) static: $(STLIBNAME)
ifeq ($(USE_SSL),1)
dynamic: $(SSL_DYLIBNAME)
static: $(SSL_STLIBNAME)
endif
# Binaries: # Binaries:
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) 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) 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) 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) 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) 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 ifndef AE_DIR
hiredis-example-ae: hiredis-example-ae:
@ -116,7 +161,7 @@ hiredis-example-libuv:
@false @false
else else
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) 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 endif
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) 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 endif
hiredis-example: examples/example.c $(STLIBNAME) 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) 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) hiredis-%: %.o $(STLIBNAME)
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
test: hiredis-test test: hiredis-test
./hiredis-test ./hiredis-test
check: hiredis-test check: hiredis-test
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - TEST_SSL=$(USE_SSL) ./test.sh
$(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`
.c.o: .c.o:
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
clean: 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: dep:
$(CC) -MM *.c $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
INSTALL?= cp -pPR INSTALL?= cp -pPR
@ -175,6 +221,20 @@ $(PKGCONFNAME): hiredis.h
@echo Libs: -L\$${libdir} -lhiredis >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ @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) install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH) $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)

View File

@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
```c ```c
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); 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 ### Sending commands and their callbacks
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.

View File

@ -34,48 +34,113 @@
#include "../hiredis.h" #include "../hiredis.h"
#include "../async.h" #include "../async.h"
#define REDIS_LIBEVENT_DELETED 0x01
#define REDIS_LIBEVENT_ENTERED 0x02
typedef struct redisLibeventEvents { typedef struct redisLibeventEvents {
redisAsyncContext *context; redisAsyncContext *context;
struct event *rev, *wev; struct event *ev;
struct event_base *base;
struct timeval tv;
short flags;
short state;
} redisLibeventEvents; } redisLibeventEvents;
static void redisLibeventReadEvent(int fd, short event, void *arg) { static void redisLibeventDestroy(redisLibeventEvents *e) {
((void)fd); ((void)event); free(e);
redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleRead(e->context);
} }
static void redisLibeventWriteEvent(int fd, short event, void *arg) { static void redisLibeventHandler(int fd, short event, void *arg) {
((void)fd); ((void)event); ((void)fd);
redisLibeventEvents *e = (redisLibeventEvents*)arg; 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) { static void redisLibeventAddRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_READ, 0);
event_add(e->rev,NULL);
} }
static void redisLibeventDelRead(void *privdata) { static void redisLibeventDelRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_READ, 1);
event_del(e->rev);
} }
static void redisLibeventAddWrite(void *privdata) { static void redisLibeventAddWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_WRITE, 0);
event_add(e->wev,NULL);
} }
static void redisLibeventDelWrite(void *privdata) { static void redisLibeventDelWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_WRITE, 1);
event_del(e->wev);
} }
static void redisLibeventCleanup(void *privdata) { static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_free(e->rev); if (!e) {
event_free(e->wev); return;
free(e); }
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) { 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; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisLibeventEvents*)malloc(sizeof(*e)); e = (redisLibeventEvents*)calloc(1, sizeof(*e));
e->context = ac; e->context = ac;
/* Register functions to start/stop listening for events */ /* 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.addWrite = redisLibeventAddWrite;
ac->ev.delWrite = redisLibeventDelWrite; ac->ev.delWrite = redisLibeventDelWrite;
ac->ev.cleanup = redisLibeventCleanup; ac->ev.cleanup = redisLibeventCleanup;
ac->ev.scheduleTimer = redisLibeventSetTimeout;
ac->ev.data = e; ac->ev.data = e;
/* Initialize and install read/write events */ /* Initialize and install read/write events */
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); e->base = base;
event_add(e->rev, NULL);
event_add(e->wev, NULL);
return REDIS_OK; return REDIS_OK;
} }
#endif #endif

View File

@ -5,8 +5,9 @@ environment:
CC: gcc CC: gcc
- CYG_BASH: C:\cygwin\bin\bash - CYG_BASH: C:\cygwin\bin\bash
CC: gcc CC: gcc
TARGET: 32bit CFLAGS: -m32
TARGET_VARS: 32bit-vars CXXFLAGS: -m32
LDFLAGS: -m32
clone_depth: 1 clone_depth: 1
@ -20,4 +21,4 @@ install:
build_script: build_script:
- 'echo building...' - '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"'

174
deps/hiredis/async.c vendored
View File

@ -32,7 +32,9 @@
#include "fmacros.h" #include "fmacros.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifndef _MSC_VER
#include <strings.h> #include <strings.h>
#endif
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
@ -40,22 +42,9 @@
#include "net.h" #include "net.h"
#include "dict.c" #include "dict.c"
#include "sds.h" #include "sds.h"
#include "win32.h"
#define _EL_ADD_READ(ctx) do { \ #include "async_private.h"
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);
/* Forward declaration of function in hiredis.c */ /* Forward declaration of function in hiredis.c */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); 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.addWrite = NULL;
ac->ev.delWrite = NULL; ac->ev.delWrite = NULL;
ac->ev.cleanup = NULL; ac->ev.cleanup = NULL;
ac->ev.scheduleTimer = NULL;
ac->onConnect = NULL; ac->onConnect = NULL;
ac->onDisconnect = NULL; ac->onDisconnect = NULL;
@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
ac->errstr = c->errstr; ac->errstr = c->errstr;
} }
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
redisOptions myOptions = *options;
redisContext *c; redisContext *c;
redisAsyncContext *ac; redisAsyncContext *ac;
c = redisConnectNonBlock(ip,port); myOptions.options |= REDIS_OPT_NONBLOCK;
if (c == NULL) c = redisConnectWithOptions(&myOptions);
if (c == NULL) {
return NULL; return NULL;
}
ac = redisAsyncInitialize(c); ac = redisAsyncInitialize(c);
if (ac == NULL) { if (ac == NULL) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
__redisAsyncCopyError(ac); __redisAsyncCopyError(ac);
return 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, redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); redisOptions options = {0};
redisAsyncContext *ac = redisAsyncInitialize(c); REDIS_OPTIONS_SET_TCP(&options, ip, port);
__redisAsyncCopyError(ac); options.endpoint.tcp.source_addr = source_addr;
return ac; return redisAsyncConnectWithOptions(&options);
} }
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); redisOptions options = {0};
redisAsyncContext *ac = redisAsyncInitialize(c); REDIS_OPTIONS_SET_TCP(&options, ip, port);
__redisAsyncCopyError(ac); options.options |= REDIS_OPT_REUSEADDR;
return ac; options.endpoint.tcp.source_addr = source_addr;
return redisAsyncConnectWithOptions(&options);
} }
redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisAsyncContext *redisAsyncConnectUnix(const char *path) {
redisContext *c; redisOptions options = {0};
redisAsyncContext *ac; REDIS_OPTIONS_SET_UNIX(&options, path);
return redisAsyncConnectWithOptions(&options);
c = redisConnectUnixNonBlock(path);
if (c == NULL)
return NULL;
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
__redisAsyncCopyError(ac);
return ac;
} }
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
} }
/* Helper function to make the disconnect happen and clean up. */ /* Helper function to make the disconnect happen and clean up. */
static void __redisAsyncDisconnect(redisAsyncContext *ac) { void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */ /* Make sure error is accessible if there is any */
@ -344,9 +330,15 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
c->flags |= REDIS_DISCONNECTING; 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 /* For non-clean disconnects, __redisAsyncFree() will execute pending
* callbacks with a NULL-reply. */ * 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 /* 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) { void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
c->flags |= REDIS_DISCONNECTING; 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) if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} }
@ -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. /* This function should be called when the socket is readable.
* It processes all replies that can be read and executes their callbacks. * It processes all replies that can be read and executes their callbacks.
*/ */
@ -539,28 +546,13 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
return; return;
} }
if (redisBufferRead(c) == REDIS_ERR) { c->funcs->async_read(ac);
__redisAsyncDisconnect(ac);
} else {
/* Always re-schedule reads */
_EL_ADD_READ(ac);
redisProcessCallbacks(ac);
}
} }
void redisAsyncHandleWrite(redisAsyncContext *ac) { void redisAsyncWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
int done = 0; int done = 0;
if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
return;
/* Try again later when the context is still not connected. */
if (!(c->flags & REDIS_CONNECTED))
return;
}
if (redisBufferWrite(c,&done) == REDIS_ERR) { if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} else { } else {
@ -575,6 +567,51 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
} }
} }
void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
return;
/* Try again later when the context is still not connected. */
if (!(c->flags & REDIS_CONNECTED))
return;
}
c->funcs->async_write(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 /* Sets a pointer to the first argument and its length starting at p. Returns
* the number of bytes to skip to get to the following argument. */ * the number of bytes to skip to get to the following argument. */
static const char *nextArgument(const char *start, const char **str, size_t *len) { static const char *nextArgument(const char *start, const char **str, size_t *len) {
@ -714,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status; 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;
}

View File

@ -57,6 +57,7 @@ typedef struct redisCallbackList {
/* Connection callback prototypes */ /* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(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 */ /* Context for an async connection to Redis */
typedef struct redisAsyncContext { typedef struct redisAsyncContext {
@ -81,6 +82,7 @@ typedef struct redisAsyncContext {
void (*addWrite)(void *privdata); void (*addWrite)(void *privdata);
void (*delWrite)(void *privdata); void (*delWrite)(void *privdata);
void (*cleanup)(void *privdata); void (*cleanup)(void *privdata);
void (*scheduleTimer)(void *privdata, struct timeval tv);
} ev; } ev;
/* Called when either the connection is terminated due to an error or per /* Called when either the connection is terminated due to an error or per
@ -106,6 +108,7 @@ typedef struct redisAsyncContext {
} redisAsyncContext; } redisAsyncContext;
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
@ -113,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
redisAsyncContext *redisAsyncConnectUnix(const char *path); redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac);
/* Handle read/write events */ /* Handle read/write events */
void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(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 /* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */ * output buffer and register the provided callback. */

72
deps/hiredis/async_private.h vendored Normal file
View File

@ -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 */

46
deps/hiredis/examples/CMakeLists.txt vendored Normal file
View File

@ -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)

View File

@ -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;
}

View File

@ -9,7 +9,12 @@
void getCallback(redisAsyncContext *c, void *r, void *privdata) { void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r; 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); printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */ /* Disconnect after receiving the reply to GET */
@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
int main (int argc, char **argv) { int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new(); 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) { if (c->err) {
/* Let *c leak for now... */ /* Let *c leak for now... */
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);

97
deps/hiredis/examples/example-ssl.c vendored Normal file
View File

@ -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;
}

View File

@ -5,14 +5,27 @@
#include <hiredis.h> #include <hiredis.h>
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j; unsigned int j, isunix = 0;
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; 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; int port = (argc > 2) ? atoi(argv[2]) : 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds 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 == NULL || c->err) {
if (c) { if (c) {
printf("Connection error: %s\n", c->errstr); printf("Connection error: %s\n", c->errstr);

223
deps/hiredis/hiredis.c vendored
View File

@ -34,7 +34,6 @@
#include "fmacros.h" #include "fmacros.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <ctype.h> #include <ctype.h>
@ -42,10 +41,20 @@
#include "hiredis.h" #include "hiredis.h"
#include "net.h" #include "net.h"
#include "sds.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 redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len); 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 *createIntegerObject(const redisReadTask *task, long long value);
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task); static void *createNilObject(const redisReadTask *task);
@ -138,7 +147,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
return r; return r;
} }
static void *createArrayObject(const redisReadTask *task, int elements) { static void *createArrayObject(const redisReadTask *task, size_t elements) {
redisReply *r, *parent; redisReply *r, *parent;
r = createReplyObject(task->type); r = createReplyObject(task->type);
@ -649,29 +658,30 @@ redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions); return redisReaderCreateWithFunctions(&defaultFunctions);
} }
static redisContext *redisContextInit(void) { static redisContext *redisContextInit(const redisOptions *options) {
redisContext *c; redisContext *c;
c = calloc(1,sizeof(redisContext)); c = calloc(1, sizeof(*c));
if (c == NULL) if (c == NULL)
return NULL; return NULL;
c->funcs = &redisContextDefaultFuncs;
c->obuf = sdsempty(); c->obuf = sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
c->fd = REDIS_INVALID_FD;
if (c->obuf == NULL || c->reader == NULL) { if (c->obuf == NULL || c->reader == NULL) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
(void)options; /* options are used in other functions */
return c; return c;
} }
void redisFree(redisContext *c) { void redisFree(redisContext *c) {
if (c == NULL) if (c == NULL)
return; return;
if (c->fd > 0) redisNetClose(c);
close(c->fd);
sdsfree(c->obuf); sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
@ -680,12 +690,16 @@ void redisFree(redisContext *c) {
free(c->unix_sock.path); free(c->unix_sock.path);
free(c->timeout); free(c->timeout);
free(c->saddr); free(c->saddr);
if (c->funcs->free_privdata) {
c->funcs->free_privdata(c->privdata);
}
memset(c, 0xff, sizeof(*c));
free(c); free(c);
} }
int redisFreeKeepFd(redisContext *c) { redisFD redisFreeKeepFd(redisContext *c) {
int fd = c->fd; redisFD fd = c->fd;
c->fd = -1; c->fd = REDIS_INVALID_FD;
redisFree(c); redisFree(c);
return fd; return fd;
} }
@ -694,10 +708,13 @@ int redisReconnect(redisContext *c) {
c->err = 0; c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr)); memset(c->errstr, '\0', strlen(c->errstr));
if (c->fd > 0) { if (c->privdata && c->funcs->free_privdata) {
close(c->fd); c->funcs->free_privdata(c->privdata);
c->privdata = NULL;
} }
redisNetClose(c);
sdsfree(c->obuf); sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
@ -718,112 +735,107 @@ int redisReconnect(redisContext *c) {
return REDIS_ERR; 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 /* 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. * 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. */ * When no set of reply functions is given, the default set will be used. */
redisContext *redisConnect(const char *ip, int port) { redisContext *redisConnect(const char *ip, int port) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
c = redisContextInit(); return redisConnectWithOptions(&options);
if (c == NULL)
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectTcp(c,ip,port,NULL);
return c;
} }
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
c = redisContextInit(); options.timeout = &tv;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectTcp(c,ip,port,&tv);
return c;
} }
redisContext *redisConnectNonBlock(const char *ip, int port) { redisContext *redisConnectNonBlock(const char *ip, int port) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
c = redisContextInit(); options.options |= REDIS_OPT_NONBLOCK;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags &= ~REDIS_BLOCK;
redisContextConnectTcp(c,ip,port,NULL);
return c;
} }
redisContext *redisConnectBindNonBlock(const char *ip, int port, redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisContextInit(); redisOptions options = {0};
if (c == NULL) REDIS_OPTIONS_SET_TCP(&options, ip, port);
return NULL; options.endpoint.tcp.source_addr = source_addr;
c->flags &= ~REDIS_BLOCK; options.options |= REDIS_OPT_NONBLOCK;
redisContextConnectBindTcp(c,ip,port,NULL,source_addr); return redisConnectWithOptions(&options);
return c;
} }
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisContextInit(); redisOptions options = {0};
if (c == NULL) REDIS_OPTIONS_SET_TCP(&options, ip, port);
return NULL; options.endpoint.tcp.source_addr = source_addr;
c->flags &= ~REDIS_BLOCK; options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
c->flags |= REDIS_REUSEADDR; return redisConnectWithOptions(&options);
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
return c;
} }
redisContext *redisConnectUnix(const char *path) { redisContext *redisConnectUnix(const char *path) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path);
c = redisContextInit(); return redisConnectWithOptions(&options);
if (c == NULL)
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectUnix(c,path,NULL);
return c;
} }
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path);
c = redisContextInit(); options.timeout = &tv;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectUnix(c,path,&tv);
return c;
} }
redisContext *redisConnectUnixNonBlock(const char *path) { redisContext *redisConnectUnixNonBlock(const char *path) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path);
c = redisContextInit(); options.options |= REDIS_OPT_NONBLOCK;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags &= ~REDIS_BLOCK;
redisContextConnectUnix(c,path,NULL);
return c;
} }
redisContext *redisConnectFd(int fd) { redisContext *redisConnectFd(redisFD fd) {
redisContext *c; redisOptions options = {0};
options.type = REDIS_CONN_USERFD;
c = redisContextInit(); options.endpoint.fd = fd;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->fd = fd;
c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
return c;
} }
/* Set read/write timeout on a blocking socket. */ /* Set read/write timeout on a blocking socket. */
@ -853,22 +865,15 @@ int redisBufferRead(redisContext *c) {
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
nread = read(c->fd,buf,sizeof(buf)); nread = c->funcs->read(c, buf, sizeof(buf));
if (nread == -1) { if (nread > 0) {
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
/* Try again later */ __redisSetError(c, c->reader->err, c->reader->errstr);
return REDIS_ERR;
} else { } else {
__redisSetError(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
} }
} else if (nread == 0) { } else if (nread < 0) {
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
return REDIS_ERR; 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; return REDIS_OK;
} }
@ -883,21 +888,15 @@ int redisBufferRead(redisContext *c) {
* c->errstr to hold the appropriate error string. * c->errstr to hold the appropriate error string.
*/ */
int redisBufferWrite(redisContext *c, int *done) { int redisBufferWrite(redisContext *c, int *done) {
int nwritten;
/* Return early when the context has seen an error. */ /* Return early when the context has seen an error. */
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
if (sdslen(c->obuf) > 0) { if (sdslen(c->obuf) > 0) {
nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); int nwritten = c->funcs->write(c);
if (nwritten == -1) { if (nwritten < 0) {
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { return REDIS_ERR;
/* Try again later */
} else {
__redisSetError(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
} else if (nwritten > 0) { } else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) { if (nwritten == (signed)sdslen(c->obuf)) {
sdsfree(c->obuf); sdsfree(c->obuf);

101
deps/hiredis/hiredis.h vendored
View File

@ -35,7 +35,11 @@
#define __HIREDIS_H #define __HIREDIS_H
#include "read.h" #include "read.h"
#include <stdarg.h> /* for va_list */ #include <stdarg.h> /* for va_list */
#ifndef _MSC_VER
#include <sys/time.h> /* for struct timeval */ #include <sys/time.h> /* for struct timeval */
#else
struct timeval; /* forward declaration */
#endif
#include <stdint.h> /* uintXX_t, etc */ #include <stdint.h> /* uintXX_t, etc */
#include "sds.h" /* for sds */ #include "sds.h" /* for sds */
@ -74,6 +78,12 @@
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ /* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80 #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 */ #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and /* number of times we retry to connect in the case of EADDRNOTAVAIL and
@ -111,14 +121,93 @@ void redisFreeSdsCommand(sds cmd);
enum redisConnectionType { enum redisConnectionType {
REDIS_CONN_TCP, 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 */ /* Context for a connection to Redis */
typedef struct redisContext { typedef struct redisContext {
const redisContextFuncs *funcs; /* Function table */
int err; /* Error flags, 0 when there is no error */ int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */ char errstr[128]; /* String representation of error when applicable */
int fd; redisFD fd;
int flags; int flags;
char *obuf; /* Write buffer */ char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */ redisReader *reader; /* Protocol reader */
@ -139,8 +228,12 @@ typedef struct redisContext {
/* For non-blocking connect */ /* For non-blocking connect */
struct sockadr *saddr; struct sockadr *saddr;
size_t addrlen; size_t addrlen;
/* Additional private data for hiredis addons such as SSL */
void *privdata;
} redisContext; } redisContext;
redisContext *redisConnectWithOptions(const redisOptions *options);
redisContext *redisConnect(const char *ip, int port); redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectNonBlock(const char *ip, int port); redisContext *redisConnectNonBlock(const char *ip, int port);
@ -151,7 +244,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnix(const char *path);
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectUnixNonBlock(const char *path);
redisContext *redisConnectFd(int fd); redisContext *redisConnectFd(redisFD fd);
/** /**
* Reconnect the given context using the saved information. * 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 redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c); int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c); void redisFree(redisContext *c);
int redisFreeKeepFd(redisContext *c); redisFD redisFreeKeepFd(redisContext *c);
int redisBufferRead(redisContext *c); int redisBufferRead(redisContext *c);
int redisBufferWrite(redisContext *c, int *done); int redisBufferWrite(redisContext *c, int *done);

11
deps/hiredis/hiredis.pc.in vendored Normal file
View File

@ -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

53
deps/hiredis/hiredis_ssl.h vendored Normal file
View File

@ -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 */

12
deps/hiredis/hiredis_ssl.pc.in vendored Normal file
View File

@ -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

122
deps/hiredis/net.c vendored
View File

@ -34,36 +34,64 @@
#include "fmacros.h" #include "fmacros.h"
#include <sys/types.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 <fcntl.h>
#include <string.h> #include <string.h>
#include <netdb.h>
#include <errno.h> #include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <poll.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
#include "sockcompat.h"
#include "win32.h"
/* Defined in hiredis.c */ /* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str); void __redisSetError(redisContext *c, int type, const char *str);
static void redisContextCloseFd(redisContext *c) { void redisNetClose(redisContext *c) {
if (c && c->fd >= 0) { if (c && c->fd != REDIS_INVALID_FD) {
close(c->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) { static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
int errorno = errno; /* snprintf() may change errno */ int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 }; char buf[128] = { 0 };
@ -79,15 +107,15 @@ static int redisSetReuseAddr(redisContext *c) {
int on = 1; int on = 1;
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
} }
static int redisCreateSocket(redisContext *c, int type) { static int redisCreateSocket(redisContext *c, int type) {
int s; redisFD s;
if ((s = socket(type, SOCK_STREAM, 0)) == -1) { if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
return REDIS_ERR; return REDIS_ERR;
} }
@ -101,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) {
} }
static int redisSetBlocking(redisContext *c, int blocking) { static int redisSetBlocking(redisContext *c, int blocking) {
#ifndef _WIN32
int flags; int flags;
/* Set the socket nonblocking. /* Set the socket nonblocking.
@ -108,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
* interrupted by a signal. */ * interrupted by a signal. */
if ((flags = fcntl(c->fd, F_GETFL)) == -1) { if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
@ -119,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) {
if (fcntl(c->fd, F_SETFL, flags) == -1) { if (fcntl(c->fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; 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; return REDIS_OK;
} }
int redisKeepAlive(redisContext *c, int interval) { int redisKeepAlive(redisContext *c, int interval) {
int val = 1; int val = 1;
int fd = c->fd; redisFD fd = c->fd;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
@ -170,7 +207,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
int yes = 1; int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
@ -212,12 +249,12 @@ static int redisContextWaitReady(redisContext *c, long msec) {
if ((res = poll(wfd, 1, msec)) == -1) { if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} else if (res == 0) { } else if (res == 0) {
errno = ETIMEDOUT; errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
@ -230,7 +267,7 @@ static int redisContextWaitReady(redisContext *c, long msec) {
} }
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
@ -277,11 +314,18 @@ int redisCheckSocketError(redisContext *c) {
} }
int redisContextSetTimeout(redisContext *c, const struct timeval tv) { 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)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR; 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)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
return REDIS_ERR; 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, static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout, const struct timeval *timeout,
const char *source_addr) { const char *source_addr) {
int s, rv, n; redisFD s;
int rv, n;
char _port[6]; /* strlen("65535"); */ char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *bservinfo, *p, *b; struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
int blocking = (c->flags & REDIS_BLOCK); 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) { for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry: 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; continue;
c->fd = s; c->fd = s;
@ -401,16 +446,14 @@ addrretry:
} }
/* For repeat connection */ /* For repeat connection */
if (c->saddr) { free(c->saddr);
free(c->saddr);
}
c->saddr = malloc(p->ai_addrlen); c->saddr = malloc(p->ai_addrlen);
memcpy(c->saddr, p->ai_addr, p->ai_addrlen); memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
c->addrlen = p->ai_addrlen; c->addrlen = p->ai_addrlen;
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) { if (errno == EHOSTUNREACH) {
redisContextCloseFd(c); redisNetClose(c);
continue; continue;
} else if (errno == EINPROGRESS) { } else if (errno == EINPROGRESS) {
if (blocking) { if (blocking) {
@ -424,7 +467,7 @@ addrretry:
if (++reuses >= REDIS_CONNECT_RETRIES) { if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error; goto error;
} else { } else {
redisContextCloseFd(c); redisNetClose(c);
goto addrretry; goto addrretry;
} }
} else { } 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) { int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
#ifndef _WIN32
int blocking = (c->flags & REDIS_BLOCK); int blocking = (c->flags & REDIS_BLOCK);
struct sockaddr_un sa; struct sockaddr_un *sa;
long timeout_msec = -1; long timeout_msec = -1;
if (redisCreateSocket(c,AF_UNIX) < 0) 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) if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
sa.sun_family = AF_UNIX; sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); c->addrlen = sizeof(struct sockaddr_un);
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { 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) { if (errno == EINPROGRESS && !blocking) {
/* This is ok. */ /* This is ok. */
} else { } else {
@ -516,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
return REDIS_OK; 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 */
} }

4
deps/hiredis/net.h vendored
View File

@ -37,6 +37,10 @@
#include "hiredis.h" #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 redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextSetTimeout(redisContext *c, const struct timeval tv);
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);

14
deps/hiredis/read.c vendored
View File

@ -31,10 +31,10 @@
#include "fmacros.h" #include "fmacros.h"
#include <string.h> #include <string.h>
#include <strings.h>
#include <stdlib.h> #include <stdlib.h>
#ifndef _MSC_VER #ifndef _MSC_VER
#include <unistd.h> #include <unistd.h>
#include <strings.h>
#endif #endif
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
@ -44,6 +44,7 @@
#include "read.h" #include "read.h"
#include "sds.h" #include "sds.h"
#include "win32.h"
static void __redisReaderSetError(redisReader *r, int type, const char *str) { static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len; size_t len;
@ -294,9 +295,9 @@ static int processLineItem(redisReader *r) {
buf[len] = '\0'; buf[len] = '\0';
if (strcasecmp(buf,",inf") == 0) { if (strcasecmp(buf,",inf") == 0) {
d = 1.0/0.0; /* Positive infinite. */ d = INFINITY; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) { } else if (strcasecmp(buf,",-inf") == 0) {
d = -1.0/0.0; /* Nevative infinite. */ d = -INFINITY; /* Nevative infinite. */
} else { } else {
d = strtod((char*)buf,&eptr); d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
@ -430,7 +431,7 @@ static int processAggregateItem(redisReader *r) {
root = (r->ridx == 0); 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, __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range"); "Multi-bulk length out of range");
return REDIS_ERR; return REDIS_ERR;
@ -657,8 +658,11 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Emit a reply when there is one. */ /* Emit a reply when there is one. */
if (r->ridx == -1) { if (r->ridx == -1) {
if (reply != NULL) if (reply != NULL) {
*reply = r->reply; *reply = r->reply;
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
r->fn->freeObject(r->reply);
}
r->reply = NULL; r->reply = NULL;
} }
return REDIS_OK; return REDIS_OK;

3
deps/hiredis/read.h vendored
View File

@ -45,6 +45,7 @@
#define REDIS_ERR_EOF 3 /* End of file */ #define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */ #define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
#define REDIS_ERR_OTHER 2 /* Everything else... */ #define REDIS_ERR_OTHER 2 /* Everything else... */
#define REDIS_REPLY_STRING 1 #define REDIS_REPLY_STRING 1
@ -79,7 +80,7 @@ typedef struct redisReadTask {
typedef struct redisReplyObjectFunctions { typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t); 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 *(*createInteger)(const redisReadTask*, long long);
void *(*createDouble)(const redisReadTask*, double, char*, size_t); void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*); void *(*createNil)(const redisReadTask*);

31
deps/hiredis/sds.h vendored
View File

@ -34,6 +34,9 @@
#define __SDS_H #define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024) #define SDS_MAX_PREALLOC (1024*1024)
#ifdef _MSC_VER
#define __attribute__(x)
#endif
#include <sys/types.h> #include <sys/types.h>
#include <stdarg.h> #include <stdarg.h>
@ -132,20 +135,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
case SDS_TYPE_5: case SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; 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; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->len = newlen; SDS_HDR(8,s)->len = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->len = newlen; SDS_HDR(16,s)->len = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->len = newlen; SDS_HDR(32,s)->len = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->len = newlen; SDS_HDR(64,s)->len = (uint64_t)newlen;
break; break;
} }
} }
@ -156,21 +159,21 @@ static inline void sdsinclen(sds s, size_t inc) {
case SDS_TYPE_5: case SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; 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); *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
} }
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->len += inc; SDS_HDR(8,s)->len += (uint8_t)inc;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->len += inc; SDS_HDR(16,s)->len += (uint16_t)inc;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->len += inc; SDS_HDR(32,s)->len += (uint32_t)inc;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->len += inc; SDS_HDR(64,s)->len += (uint64_t)inc;
break; break;
} }
} }
@ -200,16 +203,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
/* Nothing to do, this type has no total allocation info. */ /* Nothing to do, this type has no total allocation info. */
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->alloc = newlen; SDS_HDR(8,s)->alloc = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->alloc = newlen; SDS_HDR(16,s)->alloc = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->alloc = newlen; SDS_HDR(32,s)->alloc = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->alloc = newlen; SDS_HDR(64,s)->alloc = (uint64_t)newlen;
break; break;
} }
} }

248
deps/hiredis/sockcompat.c vendored Normal file
View File

@ -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 */

91
deps/hiredis/sockcompat.h vendored Normal file
View File

@ -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 */

448
deps/hiredis/ssl.c vendored Normal file
View File

@ -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
};

93
deps/hiredis/test.c vendored
View File

@ -13,12 +13,16 @@
#include <limits.h> #include <limits.h>
#include "hiredis.h" #include "hiredis.h"
#ifdef HIREDIS_TEST_SSL
#include "hiredis_ssl.h"
#endif
#include "net.h" #include "net.h"
enum connection_type { enum connection_type {
CONN_TCP, CONN_TCP,
CONN_UNIX, CONN_UNIX,
CONN_FD CONN_FD,
CONN_SSL
}; };
struct config { struct config {
@ -33,6 +37,14 @@ struct config {
struct { struct {
const char *path; const char *path;
} unix_sock; } 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" :) */ /* The following lines make up our testing "framework" :) */
@ -93,11 +105,27 @@ static int disconnect(redisContext *c, int keep_fd) {
return -1; 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) { static redisContext *do_connect(struct config config) {
redisContext *c = NULL; redisContext *c = NULL;
if (config.type == CONN_TCP) { if (config.type == CONN_TCP) {
c = redisConnect(config.tcp.host, config.tcp.port); 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) { } else if (config.type == CONN_UNIX) {
c = redisConnectUnix(config.unix_sock.path); c = redisConnectUnix(config.unix_sock.path);
} else if (config.type == CONN_FD) { } else if (config.type == CONN_FD) {
@ -121,9 +149,21 @@ static redisContext *do_connect(struct config config) {
exit(1); exit(1);
} }
if (config.type == CONN_SSL) {
do_ssl_handshake(c, config);
}
return select_database(c); 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) { static void test_format_commands(void) {
char *cmd; char *cmd;
int len; int len;
@ -360,7 +400,8 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Set error when array > INT_MAX: "); #if LLONG_MAX > SIZE_MAX
test("Set error when array > SIZE_MAX: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
ret = redisReaderGetReply(reader,&reply); ret = redisReaderGetReply(reader,&reply);
@ -369,7 +410,6 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
#if LLONG_MAX > SIZE_MAX
test("Set error when bulk > SIZE_MAX: "); test("Set error when bulk > SIZE_MAX: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
@ -434,22 +474,23 @@ static void test_free_null(void) {
test_cond(reply == NULL); test_cond(reply == NULL);
} }
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c; redisContext *c;
struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo hints = {.ai_family = AF_INET};
struct addrinfo *ai_tmp = NULL; 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) { if (rv != 0) {
// Address does *not* exist // Address does *not* exist
test("Returns error when host cannot be resolved: "); test("Returns error when host cannot be resolved: ");
// First see if this domain name *actually* resolves to NXDOMAIN // First see if this domain name *actually* resolves to NXDOMAIN
c = redisConnect("dontexist.com", 6379); c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
test_cond( test_cond(
c->err == REDIS_ERR_OTHER && c->err == REDIS_ERR_OTHER &&
(strcmp(c->errstr, "Name or service not known") == 0 || (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, strcmp(c->errstr,
"nodename nor servname provided, or not known") == 0 || "nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 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); c = do_connect(config);
test("Does not return a reply when the command times out: "); 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_sec = 0;
tv.tv_usec = 10000; tv.tv_usec = 10000;
redisSetTimeout(c, tv); redisSetTimeout(c, tv);
@ -583,7 +625,7 @@ static void test_blocking_connection_timeouts(struct config config) {
freeReplyObject(reply); freeReplyObject(reply);
test("Reconnect properly reconnects after a timeout: "); test("Reconnect properly reconnects after a timeout: ");
redisReconnect(c); do_reconnect(c, config);
reply = redisCommand(c, "PING"); reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply); freeReplyObject(reply);
@ -591,7 +633,7 @@ static void test_blocking_connection_timeouts(struct config config) {
test("Reconnect properly uses owned parameters: "); test("Reconnect properly uses owned parameters: ");
config.tcp.host = "foo"; config.tcp.host = "foo";
config.unix_sock.path = "foo"; config.unix_sock.path = "foo";
redisReconnect(c); do_reconnect(c, config);
reply = redisCommand(c, "PING"); reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply); freeReplyObject(reply);
@ -894,6 +936,23 @@ int main(int argc, char **argv) {
throughput = 0; throughput = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
test_inherit_fd = 0; 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 { } else {
fprintf(stderr, "Invalid argument: %s\n", argv[0]); fprintf(stderr, "Invalid argument: %s\n", argv[0]);
exit(1); exit(1);
@ -922,6 +981,20 @@ int main(int argc, char **argv) {
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
if (throughput) test_throughput(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) { if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_FD; cfg.type = CONN_FD;

70
deps/hiredis/test.sh vendored Executable file
View File

@ -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}

18
deps/hiredis/win32.h vendored
View File

@ -2,10 +2,20 @@
#define _WIN32_HELPER_INCLUDE #define _WIN32_HELPER_INCLUDE
#ifdef _MSC_VER #ifdef _MSC_VER
#include <winsock2.h> /* for struct timeval */
#ifndef inline #ifndef inline
#define inline __inline #define inline __inline
#endif #endif
#ifndef strcasecmp
#define strcasecmp stricmp
#endif
#ifndef strncasecmp
#define strncasecmp strnicmp
#endif
#ifndef va_copy #ifndef va_copy
#define va_copy(d,s) ((d) = (s)) #define va_copy(d,s) ((d) = (s))
#endif #endif
@ -37,6 +47,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
return count; return count;
} }
#endif #endif
#endif /* _MSC_VER */
#endif #ifdef _WIN32
#endif #define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
#endif /* _WIN32 */
#endif /* _WIN32_HELPER_INCLUDE */

View File

@ -129,6 +129,50 @@ timeout 0
# Redis default starting with Redis 3.2.1. # Redis default starting with Redis 3.2.1.
tcp-keepalive 300 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 ##################################### ################################# GENERAL #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it. # By default Redis does not run as a daemon. Use 'yes' if you need it.

View File

@ -145,6 +145,12 @@ ifeq ($(MALLOC),jemalloc)
FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS) FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
endif 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_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS) REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL) REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
@ -164,7 +170,7 @@ endif
REDIS_SERVER_NAME=redis-server REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel 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_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_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 REDIS_BENCHMARK_NAME=redis-benchmark

View File

@ -279,8 +279,8 @@ static int anetCreateSocket(char *err, int domain) {
#define ANET_CONNECT_NONE 0 #define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1 #define ANET_CONNECT_NONBLOCK 1
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */ #define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
static int anetTcpGenericConnect(char *err, char *addr, int port, static int anetTcpGenericConnect(char *err, const char *addr, int port,
char *source_addr, int flags) const char *source_addr, int flags)
{ {
int s = ANET_ERR, rv; int s = ANET_ERR, rv;
char portstr[6]; /* strlen("65535") + 1; */ 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); 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); return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
} }
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
char *source_addr) const char *source_addr)
{ {
return anetTcpGenericConnect(err,addr,port,source_addr, return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK); ANET_CONNECT_NONBLOCK);
} }
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
char *source_addr) const char *source_addr)
{ {
return anetTcpGenericConnect(err,addr,port,source_addr, return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING); 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; int s;
struct sockaddr_un sa; struct sockaddr_un sa;
@ -411,12 +411,12 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
return s; return s;
} }
int anetUnixConnect(char *err, char *path) int anetUnixConnect(char *err, const char *path)
{ {
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE); 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); return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
} }

View File

@ -49,12 +49,12 @@
#undef ip_len #undef ip_len
#endif #endif
int anetTcpConnect(char *err, char *addr, int port); int anetTcpConnect(char *err, const char *addr, int port);
int anetTcpNonBlockConnect(char *err, char *addr, int port); int anetTcpNonBlockConnect(char *err, const char *addr, int port);
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr); int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr); int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetUnixConnect(char *err, char *path); int anetUnixConnect(char *err, const char *path);
int anetUnixNonBlockConnect(char *err, char *path); int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count); int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);

View File

@ -653,7 +653,7 @@ struct client *createFakeClient(void) {
struct client *c = zmalloc(sizeof(*c)); struct client *c = zmalloc(sizeof(*c));
selectDb(c,0); selectDb(c,0);
c->fd = -1; c->conn = NULL;
c->name = NULL; c->name = NULL;
c->querybuf = sdsempty(); c->querybuf = sdsempty();
c->querybuf_peak = 0; c->querybuf_peak = 0;

View File

@ -49,7 +49,7 @@ clusterNode *myself = NULL;
clusterNode *createClusterNode(char *nodename, int flags); clusterNode *createClusterNode(char *nodename, int flags);
int clusterAddNode(clusterNode *node); int clusterAddNode(clusterNode *node);
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); 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 clusterSendPing(clusterLink *link, int type);
void clusterSendFail(char *nodename); void clusterSendFail(char *nodename);
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request); void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
@ -477,7 +477,8 @@ void clusterInit(void) {
/* Port sanity check II /* Port sanity check II
* The other handshake port check is triggered too late to stop * The other handshake port check is triggered too late to stop
* us from trying to use a too-high cluster port number. */ * 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. " serverLog(LL_WARNING, "Redis port number too high. "
"Cluster communication port is 10,000 port " "Cluster communication port is 10,000 port "
"numbers higher than your Redis port. " "numbers higher than your Redis port. "
@ -485,8 +486,7 @@ void clusterInit(void) {
"lower than 55535."); "lower than 55535.");
exit(1); exit(1);
} }
if (listenToPort(port+CLUSTER_PORT_INCR,
if (listenToPort(server.port+CLUSTER_PORT_INCR,
server.cfd,&server.cfd_count) == C_ERR) server.cfd,&server.cfd_count) == C_ERR)
{ {
exit(1); exit(1);
@ -508,8 +508,8 @@ void clusterInit(void) {
/* Set myself->port / cport to my listening ports, we'll just need to /* Set myself->port / cport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */ * discover the IP address via MEET messages. */
myself->port = server.port; myself->port = port;
myself->cport = server.port+CLUSTER_PORT_INCR; myself->cport = port+CLUSTER_PORT_INCR;
if (server.cluster_announce_port) if (server.cluster_announce_port)
myself->port = server.cluster_announce_port; myself->port = server.cluster_announce_port;
if (server.cluster_announce_bus_port) if (server.cluster_announce_bus_port)
@ -593,7 +593,7 @@ clusterLink *createClusterLink(clusterNode *node) {
link->sndbuf = sdsempty(); link->sndbuf = sdsempty();
link->rcvbuf = sdsempty(); link->rcvbuf = sdsempty();
link->node = node; link->node = node;
link->fd = -1; link->conn = NULL;
return link; return link;
} }
@ -601,23 +601,45 @@ clusterLink *createClusterLink(clusterNode *node) {
* This function will just make sure that the original node associated * This function will just make sure that the original node associated
* with this link will have the 'link' field set to NULL. */ * with this link will have the 'link' field set to NULL. */
void freeClusterLink(clusterLink *link) { void freeClusterLink(clusterLink *link) {
if (link->fd != -1) { if (link->conn) {
aeDeleteFileEvent(server.el, link->fd, AE_READABLE|AE_WRITABLE); connClose(link->conn);
link->conn = NULL;
} }
sdsfree(link->sndbuf); sdsfree(link->sndbuf);
sdsfree(link->rcvbuf); sdsfree(link->rcvbuf);
if (link->node) if (link->node)
link->node->link = NULL; link->node->link = NULL;
close(link->fd);
zfree(link); 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 #define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd; int cport, cfd;
int max = MAX_CLUSTER_ACCEPTS_PER_CALL; int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
char cip[NET_IP_STR_LEN]; char cip[NET_IP_STR_LEN];
clusterLink *link;
UNUSED(el); UNUSED(el);
UNUSED(mask); UNUSED(mask);
UNUSED(privdata); UNUSED(privdata);
@ -634,19 +656,24 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
"Error accepting cluster node: %s", server.neterr); "Error accepting cluster node: %s", server.neterr);
return; 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. */ /* Use non-blocking I/O for cluster messages. */
serverLog(LL_VERBOSE,"Accepted cluster node %s:%d", cip, cport); serverLog(LL_VERBOSE,"Accepting cluster node connection from %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. /* Accept the connection now. connAccept() may call our handler directly
* Initiallly the link->node pointer is set to NULL as we don't know * or schedule it for later depending on connection implementation.
* which node is, but the right node is references once we know the */
* node identity. */ if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
link = createClusterLink(NULL); serverLog(LL_VERBOSE,
link->fd = cfd; "Error accepting cluster node connection: %s",
aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link); 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); memcpy(buf,announced_ip,NET_IP_STR_LEN);
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */ buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
} else { } 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]; 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)) strcmp(ip,myself->ip))
{ {
memcpy(myself->ip,ip,NET_IP_STR_LEN); 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 /* 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 * consumed by write(). We don't try to optimize this for speed too much
* as this is a very low traffic channel. */ * as this is a very low traffic channel. */
void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) { void clusterWriteHandler(connection *conn) {
clusterLink *link = (clusterLink*) privdata; clusterLink *link = connGetPrivateData(conn);
ssize_t nwritten; 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) { if (nwritten <= 0) {
serverLog(LL_DEBUG,"I/O error writing to node link: %s", 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); handleLinkIOError(link);
return; return;
} }
sdsrange(link->sndbuf,nwritten,-1); sdsrange(link->sndbuf,nwritten,-1);
if (sdslen(link->sndbuf) == 0) 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 /* 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 * 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. */ * 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)]; char buf[sizeof(clusterMsg)];
ssize_t nread; ssize_t nread;
clusterMsg *hdr; clusterMsg *hdr;
clusterLink *link = (clusterLink*) privdata; clusterLink *link = connGetPrivateData(conn);
unsigned int readlen, rcvbuflen; unsigned int readlen, rcvbuflen;
UNUSED(el);
UNUSED(mask);
while(1) { /* Read as long as there is data to read. */ while(1) { /* Read as long as there is data to read. */
rcvbuflen = sdslen(link->rcvbuf); 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); if (readlen > sizeof(buf)) readlen = sizeof(buf);
} }
nread = read(fd,buf,readlen); nread = connRead(conn,buf,readlen);
if (nread == -1 && errno == EAGAIN) return; /* No more data ready. */ if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */
if (nread <= 0) { if (nread <= 0) {
/* I/O error... */ /* I/O error... */
serverLog(LL_DEBUG,"I/O error reading from node link: %s", 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); handleLinkIOError(link);
return; return;
} else { } 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. */ * from event handlers that will do stuff with the same link later. */
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) { void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
if (sdslen(link->sndbuf) == 0 && msglen != 0) if (sdslen(link->sndbuf) == 0 && msglen != 0)
aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER, connSetWriteHandler(link->conn, clusterWriteHandler); /* TODO: Handle AE_BARRIER in conns */
clusterWriteHandler,link);
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen); link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
@ -2276,11 +2343,12 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
} }
/* Handle cluster-announce-port as well. */ /* Handle cluster-announce-port as well. */
int port = server.tls_cluster ? server.tls_port : server.port;
int announced_port = server.cluster_announce_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 ? int announced_cport = server.cluster_announce_bus_port ?
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)); memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
memset(hdr->slaveof,0,CLUSTER_NAMELEN); memset(hdr->slaveof,0,CLUSTER_NAMELEN);
@ -3383,13 +3451,11 @@ void clusterCron(void) {
} }
if (node->link == NULL) { if (node->link == NULL) {
int fd; clusterLink *link = createClusterLink(node);
mstime_t old_ping_sent; link->conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
clusterLink *link; connSetPrivateData(link->conn, link);
if (connConnect(link->conn, node->ip, node->cport, NET_FIRST_BIND_ADDR,
fd = anetTcpNonBlockBindConnect(server.neterr, node->ip, clusterLinkConnectHandler) == -1) {
node->cport, NET_FIRST_BIND_ADDR);
if (fd == -1) {
/* We got a synchronous error from connect before /* We got a synchronous error from connect before
* clusterSendPing() had a chance to be called. * clusterSendPing() had a chance to be called.
* If node->ping_sent is zero, failure detection can't work, * 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 " serverLog(LL_DEBUG, "Unable to connect to "
"Cluster Node [%s]:%d -> %s", node->ip, "Cluster Node [%s]:%d -> %s", node->ip,
node->cport, server.neterr); node->cport, server.neterr);
freeClusterLink(link);
continue; continue;
} }
link = createClusterLink(node);
link->fd = fd;
node->link = link; 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); dictReleaseIterator(di);
@ -4940,7 +4980,7 @@ void restoreCommand(client *c) {
#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */ #define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
typedef struct migrateCachedSocket { typedef struct migrateCachedSocket {
int fd; connection *conn;
long last_dbid; long last_dbid;
time_t last_use_time; time_t last_use_time;
} migrateCachedSocket; } migrateCachedSocket;
@ -4957,7 +4997,7 @@ typedef struct migrateCachedSocket {
* should be called so that the connection will be created from scratch * should be called so that the connection will be created from scratch
* the next time. */ * the next time. */
migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) { migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) {
int fd; connection *conn;
sds name = sdsempty(); sds name = sdsempty();
migrateCachedSocket *cs; migrateCachedSocket *cs;
@ -4977,34 +5017,27 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
/* Too many items, drop one at random. */ /* Too many items, drop one at random. */
dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets); dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
cs = dictGetVal(de); cs = dictGetVal(de);
close(cs->fd); connClose(cs->conn);
zfree(cs); zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de)); dictDelete(server.migrate_cached_sockets,dictGetKey(de));
} }
/* Create the socket */ /* Create the socket */
fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr, conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
atoi(c->argv[2]->ptr)); if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
if (fd == -1) { != C_OK) {
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);
addReplySds(c, addReplySds(c,
sdsnew("-IOERR error or timeout connecting to the client\r\n")); sdsnew("-IOERR error or timeout connecting to the client\r\n"));
close(fd); connClose(conn);
sdsfree(name);
return NULL; return NULL;
} }
connEnableTcpNoDelay(conn);
/* Add to the cache and return it to the caller. */ /* Add to the cache and return it to the caller. */
cs = zmalloc(sizeof(*cs)); cs = zmalloc(sizeof(*cs));
cs->fd = fd; cs->conn = conn;
cs->last_dbid = -1; cs->last_dbid = -1;
cs->last_use_time = server.unixtime; cs->last_use_time = server.unixtime;
dictAdd(server.migrate_cached_sockets,name,cs); dictAdd(server.migrate_cached_sockets,name,cs);
@ -5025,7 +5058,7 @@ void migrateCloseSocket(robj *host, robj *port) {
return; return;
} }
close(cs->fd); connClose(cs->conn);
zfree(cs); zfree(cs);
dictDelete(server.migrate_cached_sockets,name); dictDelete(server.migrate_cached_sockets,name);
sdsfree(name); sdsfree(name);
@ -5039,7 +5072,7 @@ void migrateCloseTimedoutSockets(void) {
migrateCachedSocket *cs = dictGetVal(de); migrateCachedSocket *cs = dictGetVal(de);
if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) { if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
close(cs->fd); connClose(cs->conn);
zfree(cs); zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de)); dictDelete(server.migrate_cached_sockets,dictGetKey(de));
} }
@ -5221,7 +5254,7 @@ try_again:
while ((towrite = sdslen(buf)-pos) > 0) { while ((towrite = sdslen(buf)-pos) > 0) {
towrite = (towrite > (64*1024) ? (64*1024) : towrite); 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) { if (nwritten != (signed)towrite) {
write_error = 1; write_error = 1;
goto socket_err; goto socket_err;
@ -5235,11 +5268,11 @@ try_again:
char buf2[1024]; /* Restore reply. */ char buf2[1024]; /* Restore reply. */
/* Read the AUTH reply if needed. */ /* 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; goto socket_err;
/* Read the SELECT reply if needed. */ /* 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; goto socket_err;
/* Read the RESTORE replies. */ /* Read the RESTORE replies. */
@ -5254,7 +5287,7 @@ try_again:
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1)); if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
for (j = 0; j < num_keys; j++) { 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; socket_error = 1;
break; break;
} }

View File

@ -40,7 +40,7 @@ struct clusterNode;
/* clusterLink encapsulates everything needed to talk with a remote node. */ /* clusterLink encapsulates everything needed to talk with a remote node. */
typedef struct clusterLink { typedef struct clusterLink {
mstime_t ctime; /* Link creation time */ mstime_t ctime; /* Link creation time */
int fd; /* TCP socket file descriptor */ connection *conn; /* Connection to remote node */
sds sndbuf; /* Packet send buffer */ sds sndbuf; /* Packet send buffer */
sds rcvbuf; /* Packet reception buffer */ sds rcvbuf; /* Packet reception buffer */
struct clusterNode *node; /* Node related to this link if any, or NULL */ struct clusterNode *node; /* Node related to this link if any, or NULL */

View File

@ -286,6 +286,15 @@ void loadServerConfigFromString(char *config) {
if (server.port < 0 || server.port > 65535) { if (server.port < 0 || server.port > 65535) {
err = "Invalid port"; goto loaderr; 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) { } else if (!strcasecmp(argv[0],"tcp-backlog") && argc == 2) {
server.tcp_backlog = atoi(argv[1]); server.tcp_backlog = atoi(argv[1]);
if (server.tcp_backlog < 0) { if (server.tcp_backlog < 0) {
@ -791,6 +800,24 @@ void loadServerConfigFromString(char *config) {
err = sentinelHandleConfiguration(argv+1,argc-1); err = sentinelHandleConfiguration(argv+1,argc-1);
if (err) goto loaderr; 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 { } else {
err = "Bad directive or wrong number of arguments"; goto loaderr; err = "Bad directive or wrong number of arguments"; goto loaderr;
} }
@ -1234,6 +1261,45 @@ void configSetCommand(client *c) {
} config_set_enum_field( } config_set_enum_field(
"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) { "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... */ /* Everyhing else is an error... */
} config_set_else { } config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", 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("pidfile",server.pidfile);
config_get_string_field("slave-announce-ip",server.slave_announce_ip); 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("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 */ /* Numerical values */
config_get_numerical_field("maxmemory",server.maxmemory); 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("slowlog-max-len", server.slowlog_max_len);
config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill); config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill);
config_get_numerical_field("port",server.port); 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-port",server.cluster_announce_port);
config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port); config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
config_get_numerical_field("tcp-backlog",server.tcp_backlog); 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("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 */ /* Enum values */
config_get_enum_field("maxmemory-policy", config_get_enum_field("maxmemory-policy",
@ -2113,10 +2187,13 @@ int rewriteConfig(char *path) {
} }
rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE); 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-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,"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); 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); rewriteConfigBindOption(state);
rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL); rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL);
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM); 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); rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE); 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); 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. */ /* Rewrite Sentinel config if in Sentinel mode. */
if (server.sentinel_mode) rewriteConfigSentinelOption(state); if (server.sentinel_mode) rewriteConfigSentinelOption(state);

383
src/connection.c Normal file
View File

@ -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;
}

211
src/connection.h Normal file
View File

@ -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 */

60
src/connhelpers.h Normal file
View File

@ -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 */

View File

@ -699,11 +699,12 @@ void _serverAssert(const char *estr, const char *file, int line) {
void _serverAssertPrintClientInfo(const client *c) { void _serverAssertPrintClientInfo(const client *c) {
int j; int j;
char conninfo[CONN_INFO_LEN];
bugReportStart(); bugReportStart();
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ==="); serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long)c->flags); serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long) c->flags);
serverLog(LL_WARNING,"client->fd = %d", c->fd); serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
serverLog(LL_WARNING,"client->argc = %d", c->argc); serverLog(LL_WARNING,"client->argc = %d", c->argc);
for (j=0; j < c->argc; j++) { for (j=0; j < c->argc; j++) {
char buf[128]; char buf[128];

View File

@ -2764,7 +2764,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Create the client and dispatch the command. */ /* Create the client and dispatch the command. */
va_start(ap, fmt); va_start(ap, fmt);
c = createClient(-1); c = createClient(NULL);
c->user = NULL; /* Root user. */ c->user = NULL; /* Root user. */
argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap); argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap);
replicate = flags & REDISMODULE_ARGV_REPLICATE; replicate = flags & REDISMODULE_ARGV_REPLICATE;
@ -3681,7 +3681,7 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
bc->disconnect_callback = NULL; /* Set by RM_SetDisconnectCallback() */ bc->disconnect_callback = NULL; /* Set by RM_SetDisconnectCallback() */
bc->free_privdata = free_privdata; bc->free_privdata = free_privdata;
bc->privdata = NULL; bc->privdata = NULL;
bc->reply_client = createClient(-1); bc->reply_client = createClient(NULL);
bc->reply_client->flags |= CLIENT_MODULE; bc->reply_client->flags |= CLIENT_MODULE;
bc->dbid = c->db->id; bc->dbid = c->db->id;
c->bpop.timeout = timeout_ms ? (mstime()+timeout_ms) : 0; 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 * 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 * in order to keep things like the currently selected database and similar
* things. */ * things. */
ctx->client = createClient(-1); ctx->client = createClient(NULL);
if (bc) { if (bc) {
selectDb(ctx->client,bc->dbid); selectDb(ctx->client,bc->dbid);
ctx->client->id = bc->client->id; ctx->client->id = bc->client->id;
@ -5113,7 +5113,7 @@ void moduleInitModulesSystem(void) {
/* Set up the keyspace notification susbscriber list and static client */ /* Set up the keyspace notification susbscriber list and static client */
moduleKeyspaceSubscribers = listCreate(); moduleKeyspaceSubscribers = listCreate();
moduleFreeContextReusedClient = createClient(-1); moduleFreeContextReusedClient = createClient(NULL);
moduleFreeContextReusedClient->flags |= CLIENT_MODULE; moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
moduleFreeContextReusedClient->user = NULL; /* root user. */ moduleFreeContextReusedClient->user = NULL; /* root user. */

View File

@ -84,32 +84,27 @@ void linkClient(client *c) {
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL); raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
} }
client *createClient(int fd) { client *createClient(connection *conn) {
client *c = zmalloc(sizeof(client)); 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 * This is useful since all the commands needs to be executed
* in the context of a client. When commands are executed in other * in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */ * contexts (for instance a Lua script) we need a non connected client. */
if (fd != -1) { if (conn) {
anetNonBlock(NULL,fd); connNonBlock(conn);
anetEnableTcpNoDelay(NULL,fd); connEnableTcpNoDelay(conn);
if (server.tcpkeepalive) if (server.tcpkeepalive)
anetKeepAlive(NULL,fd,server.tcpkeepalive); connKeepAlive(conn,server.tcpkeepalive);
if (aeCreateFileEvent(server.el,fd,AE_READABLE, connSetReadHandler(conn, readQueryFromClient);
readQueryFromClient, c) == AE_ERR) connSetPrivateData(conn, c);
{
close(fd);
zfree(c);
return NULL;
}
} }
selectDb(c,0); selectDb(c,0);
uint64_t client_id = ++server.next_client_id; uint64_t client_id = ++server.next_client_id;
c->id = client_id; c->id = client_id;
c->resp = 2; c->resp = 2;
c->fd = fd; c->conn = conn;
c->name = NULL; c->name = NULL;
c->bufpos = 0; c->bufpos = 0;
c->qb_pos = 0; c->qb_pos = 0;
@ -161,7 +156,7 @@ client *createClient(int fd) {
c->client_tracking_redirection = 0; c->client_tracking_redirection = 0;
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid); listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects); listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
if (fd != -1) linkClient(c); if (conn) linkClient(c);
initClientMultiState(c); initClientMultiState(c);
return c; return c;
} }
@ -227,7 +222,7 @@ int prepareClientToWrite(client *c) {
if ((c->flags & CLIENT_MASTER) && if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR; !(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 /* 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). */ * 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); return c->bufpos || listLength(c->reply);
} }
#define MAX_ACCEPTS_PER_CALL 1000 void clientAcceptHandler(connection *conn) {
static void acceptCommonHandler(int fd, int flags, char *ip) { client *c = connGetPrivateData(conn);
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";
/* That's a best effort error message, don't check write errors */ if (connGetState(conn) != CONN_STATE_CONNECTED) {
if (write(c->fd,err,strlen(err)) == -1) { serverLog(LL_WARNING,
/* Nothing to do, Just to avoid the warning... */ "Error accepting a client connection: %s",
} connGetLastError(conn));
server.stat_rejected_conn++;
freeClient(c); freeClient(c);
return; return;
} }
@ -810,10 +790,12 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
if (server.protected_mode && if (server.protected_mode &&
server.bindaddr_count == 0 && server.bindaddr_count == 0 &&
DefaultUser->flags & USER_FLAG_NOPASS && DefaultUser->flags & USER_FLAG_NOPASS &&
!(flags & CLIENT_UNIX_SOCKET) && !(c->flags & CLIENT_UNIX_SOCKET))
ip != NULL)
{ {
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 = char *err =
"-DENIED Redis is running in protected mode because protected " "-DENIED Redis is running in protected mode because protected "
"mode is enabled, no bind address was specified, no " "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. " "4) Setup a bind address or an authentication password. "
"NOTE: You only need to do one of the above things in order for " "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"; "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... */ /* Nothing to do, Just to avoid the warning... */
} }
server.stat_rejected_conn++; server.stat_rejected_conn++;
@ -845,7 +827,63 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
} }
server.stat_numconnections++; 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; 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) { 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; return;
} }
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport); 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; return;
} }
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket); 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 this is marked as current client unset it. */
if (server.current_client == c) server.current_client = NULL; 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 * If the client was already unlinked or if it's a "fake client" the
* fd is already set to -1. */ * conn is already set to NULL. */
if (c->fd != -1) { if (c->conn) {
/* Remove from the list of active clients. */ /* Remove from the list of active clients. */
if (c->client_list_node) { if (c->client_list_node) {
uint64_t id = htonu64(c->id); 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 * shutdown the socket the fork will continue to write to the slave
* and the salve will only find out that it was disconnected when * and the salve will only find out that it was disconnected when
* it will finish reading the rdb. */ * it will finish reading the rdb. */
if ((c->flags & CLIENT_SLAVE) && int need_shutdown = ((c->flags & CLIENT_SLAVE) &&
(c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)) { (c->replstate == SLAVE_STATE_WAIT_BGSAVE_END));
shutdown(c->fd, SHUT_RDWR); if (need_shutdown) connShutdown(c->conn, SHUT_RDWR);
} connClose(c->conn);
c->conn = NULL;
/* 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;
} }
/* Remove from the list of pending writes if needed. */ /* 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 /* 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 * 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 * 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 * set to 0. So when handler_installed is set to 0 the function must be
* thread safe. */ * thread safe. */
int writeToClient(int fd, client *c, int handler_installed) { int writeToClient(client *c, int handler_installed) {
ssize_t nwritten = 0, totwritten = 0; ssize_t nwritten = 0, totwritten = 0;
size_t objlen; size_t objlen;
clientReplyBlock *o; clientReplyBlock *o;
while(clientHasPendingReplies(c)) { while(clientHasPendingReplies(c)) {
if (c->bufpos > 0) { 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; if (nwritten <= 0) break;
c->sentlen += nwritten; c->sentlen += nwritten;
totwritten += nwritten; totwritten += nwritten;
@ -1145,7 +1199,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
continue; 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; if (nwritten <= 0) break;
c->sentlen += nwritten; c->sentlen += nwritten;
totwritten += nwritten; totwritten += nwritten;
@ -1180,11 +1234,11 @@ int writeToClient(int fd, client *c, int handler_installed) {
} }
server.stat_net_output_bytes += totwritten; server.stat_net_output_bytes += totwritten;
if (nwritten == -1) { if (nwritten == -1) {
if (errno == EAGAIN) { if (connGetState(c->conn) == CONN_STATE_CONNECTED) {
nwritten = 0; nwritten = 0;
} else { } else {
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"Error writing to client: %s", strerror(errno)); "Error writing to client: %s", connGetLastError(c->conn));
freeClientAsync(c); freeClientAsync(c);
return C_ERR; return C_ERR;
} }
@ -1202,7 +1256,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
* adDeleteFileEvent() is not thread safe: however writeToClient() * adDeleteFileEvent() is not thread safe: however writeToClient()
* is always called with handler_installed set to 0 from threads * is always called with handler_installed set to 0 from threads
* so we are fine. */ * 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. */ /* Close connection after entire reply has been sent. */
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) { 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. */ /* Write event handler. Just send data to the client. */
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { void sendReplyToClient(connection *conn) {
UNUSED(el); client *c = connGetPrivateData(conn);
UNUSED(mask); writeToClient(c,1);
writeToClient(fd,privdata,1);
} }
/* This function is called just before entering the event loop, in the hope /* 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; if (c->flags & CLIENT_PROTECTED) continue;
/* Try to write buffers to the client socket. */ /* 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 /* If after the synchronous writes above we still have data to
* output to the client, we need to install the writable handler. */ * output to the client, we need to install the writable handler. */
@ -1256,10 +1309,9 @@ int handleClientsWithPendingWrites(void) {
{ {
ae_flags |= AE_BARRIER; ae_flags |= AE_BARRIER;
} }
if (aeCreateFileEvent(server.el, c->fd, ae_flags, /* TODO: Handle write barriers in connection */
sendReplyToClient, c) == AE_ERR) if (connSetWriteHandler(c->conn, sendReplyToClient) == C_ERR) {
{ freeClientAsync(c);
freeClientAsync(c);
} }
} }
} }
@ -1305,15 +1357,15 @@ void resetClient(client *c) {
* path, it is not really released, but only marked for later release. */ * path, it is not really released, but only marked for later release. */
void protectClient(client *c) { void protectClient(client *c) {
c->flags |= CLIENT_PROTECTED; c->flags |= CLIENT_PROTECTED;
aeDeleteFileEvent(server.el,c->fd,AE_READABLE); connSetReadHandler(c->conn,NULL);
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); connSetWriteHandler(c->conn,NULL);
} }
/* This will undo the client protection done by protectClient() */ /* This will undo the client protection done by protectClient() */
void unprotectClient(client *c) { void unprotectClient(client *c) {
if (c->flags & CLIENT_PROTECTED) { if (c->flags & CLIENT_PROTECTED) {
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); if (clientHasPendingReplies(c)) clientInstallWriteHandler(c);
} }
} }
@ -1710,12 +1762,10 @@ void processInputBufferAndReplicate(client *c) {
} }
} }
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { void readQueryFromClient(connection *conn) {
client *c = (client*) privdata; client *c = connGetPrivateData(conn);
int nread, readlen; int nread, readlen;
size_t qblen; size_t qblen;
UNUSED(el);
UNUSED(mask);
/* Check if we want to read from the client later when exiting from /* 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. */ * 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); qblen = sdslen(c->querybuf);
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen; if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen); 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 (nread == -1) {
if (errno == EAGAIN) { if (connGetState(conn) == CONN_STATE_CONNECTED) {
return; return;
} else { } else {
serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno)); serverLog(LL_VERBOSE, "Reading from client: %s",connGetLastError(c->conn));
freeClientAsync(c); freeClientAsync(c);
return; return;
} }
@ -1818,7 +1868,7 @@ void genClientPeerId(client *client, char *peerid,
snprintf(peerid,peerid_len,"%s:0",server.unixsocket); snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
} else { } else {
/* TCP client. */ /* 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 /* Concatenate a string representing the state of a client in an human
* readable format, into the sds string 's'. */ * readable format, into the sds string 's'. */
sds catClientInfoString(sds s, client *client) { sds catClientInfoString(sds s, client *client) {
char flags[16], events[3], *p; char flags[16], events[3], conninfo[CONN_INFO_LEN], *p;
int emask;
p = flags; p = flags;
if (client->flags & CLIENT_SLAVE) { if (client->flags & CLIENT_SLAVE) {
@ -1864,16 +1913,17 @@ sds catClientInfoString(sds s, client *client) {
if (p == flags) *p++ = 'N'; if (p == flags) *p++ = 'N';
*p++ = '\0'; *p++ = '\0';
emask = client->fd == -1 ? 0 : aeGetFileEvents(server.el,client->fd);
p = events; p = events;
if (emask & AE_READABLE) *p++ = 'r'; if (client->conn) {
if (emask & AE_WRITABLE) *p++ = 'w'; if (connHasReadHandler(client->conn)) *p++ = 'r';
if (connHasWriteHandler(client->conn)) *p++ = 'w';
}
*p = '\0'; *p = '\0';
return sdscatfmt(s, 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, (unsigned long long) client->id,
getClientPeerId(client), getClientPeerId(client),
client->fd, connGetInfo(client->conn, conninfo, sizeof(conninfo)),
client->name ? (char*)client->name->ptr : "", client->name ? (char*)client->name->ptr : "",
(long long)(server.unixtime - client->ctime), (long long)(server.unixtime - client->ctime),
(long long)(server.unixtime - client->lastinteraction), (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 * 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. */ * lower level functions pushing data inside the client output buffers. */
void asyncCloseClientOnOutputBufferLimitReached(client *c) { 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)); serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return; if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
if (checkClientOutputBufferLimits(c)) { if (checkClientOutputBufferLimits(c)) {
@ -2468,8 +2518,7 @@ void flushSlavesOutputBuffers(void) {
listRewind(server.slaves,&li); listRewind(server.slaves,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *slave = listNodeValue(ln); client *slave = listNodeValue(ln);
int events = aeGetFileEvents(server.el,slave->fd); int can_receive_writes = connHasWriteHandler(slave->conn) ||
int can_receive_writes = (events & AE_WRITABLE) ||
(slave->flags & CLIENT_PENDING_WRITE); (slave->flags & CLIENT_PENDING_WRITE);
/* We don't want to send the pending data to the replica in a few /* 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 && !slave->repl_put_online_on_ack &&
clientHasPendingReplies(slave)) clientHasPendingReplies(slave))
{ {
writeToClient(slave->fd,slave,0); writeToClient(slave,0);
} }
} }
} }
@ -2618,9 +2667,9 @@ void *IOThreadMain(void *myid) {
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *c = listNodeValue(ln); client *c = listNodeValue(ln);
if (io_threads_op == IO_THREADS_OP_WRITE) { 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) { } else if (io_threads_op == IO_THREADS_OP_READ) {
readQueryFromClient(NULL,c->fd,c,0); readQueryFromClient(c->conn);
} else { } else {
serverPanic("io_threads_op value is unknown"); 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 /* Install the write handler if there are pending writes in some
* of the clients. */ * of the clients. */
if (clientHasPendingReplies(c) && if (clientHasPendingReplies(c) &&
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, connSetWriteHandler(c->conn, sendReplyToClient) == AE_ERR)
sendReplyToClient, c) == AE_ERR)
{ {
freeClientAsync(c); freeClientAsync(c);
} }

View File

@ -2387,8 +2387,8 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
"Slave %s correctly received the streamed RDB file.", "Slave %s correctly received the streamed RDB file.",
replicationGetSlaveName(slave)); replicationGetSlaveName(slave));
/* Restore the socket as non-blocking. */ /* Restore the socket as non-blocking. */
anetNonBlock(NULL,slave->fd); connNonBlock(slave->conn);
anetSendTimeout(NULL,slave->fd,0); 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 /* Spawn an RDB child that writes the RDB to the sockets of the slaves
* that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */ * that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) { int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
int *fds; connection **conns;
uint64_t *clientids; uint64_t *clientids;
int numfds; int numconns;
listNode *ln; listNode *ln;
listIter li; listIter li;
pid_t childpid; pid_t childpid;
@ -2445,26 +2445,26 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
/* Collect the file descriptors of the slaves we want to transfer /* Collect the file descriptors of the slaves we want to transfer
* the RDB to, which are i WAIT_BGSAVE_START state. */ * 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 /* We also allocate an array of corresponding client IDs. This will
* be useful for the child process in order to build the report * be useful for the child process in order to build the report
* (sent via unix pipe) that will be sent to the parent. */ * (sent via unix pipe) that will be sent to the parent. */
clientids = zmalloc(sizeof(uint64_t)*listLength(server.slaves)); clientids = zmalloc(sizeof(uint64_t)*listLength(server.slaves));
numfds = 0; numconns = 0;
listRewind(server.slaves,&li); listRewind(server.slaves,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *slave = ln->value; client *slave = ln->value;
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) { if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
clientids[numfds] = slave->id; clientids[numconns] = slave->id;
fds[numfds++] = slave->fd; conns[numconns++] = slave->conn;
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset()); replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
/* Put the socket in blocking mode to simplify RDB transfer. /* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the children returns (since duped socket * We'll restore it when the children returns (since duped socket
* will share the O_NONBLOCK attribute with the parent). */ * will share the O_NONBLOCK attribute with the parent). */
anetBlock(NULL,slave->fd); connBlock(slave->conn);
anetSendTimeout(NULL,slave->fd,server.repl_timeout*1000); connSendTimeout(slave->conn,server.repl_timeout*1000);
} }
} }
@ -2476,8 +2476,8 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
int retval; int retval;
rio slave_sockets; rio slave_sockets;
rioInitWithFdset(&slave_sockets,fds,numfds); rioInitWithConnset(&slave_sockets,conns,numconns);
zfree(fds); zfree(conns);
closeListeningSockets(0); closeListeningSockets(0);
redisSetProcTitle("redis-rdb-to-slaves"); redisSetProcTitle("redis-rdb-to-slaves");
@ -2513,22 +2513,22 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
* can match the report with a specific slave, and 'error' is * can match the report with a specific slave, and 'error' is
* set to 0 if the replication process terminated with a success * set to 0 if the replication process terminated with a success
* or the error code if an error occurred. */ * 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 *len = msg;
uint64_t *ids = len+1; uint64_t *ids = len+1;
int j, msglen; int j, msglen;
*len = numfds; *len = numconns;
for (j = 0; j < numfds; j++) { for (j = 0; j < numconns; j++) {
*ids++ = clientids[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 /* 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 * we are unable to transfer the message to the parent, we exit
* with an error so that the parent will abort the replication * with an error so that the parent will abort the replication
* process with all the childre that were waiting. */ * 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 || if (*len == 0 ||
write(server.rdb_pipe_write_result_to_parent,msg,msglen) write(server.rdb_pipe_write_result_to_parent,msg,msglen)
!= msglen) != msglen)
@ -2538,7 +2538,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
zfree(msg); zfree(msg);
} }
zfree(clientids); zfree(clientids);
rioFreeFdset(&slave_sockets); rioFreeConnset(&slave_sockets);
exitFromChild((retval == C_OK) ? 0 : 1); exitFromChild((retval == C_OK) ? 0 : 1);
} else { } else {
/* Parent */ /* Parent */
@ -2554,7 +2554,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
client *slave = ln->value; client *slave = ln->value;
int j; int j;
for (j = 0; j < numfds; j++) { for (j = 0; j < numconns; j++) {
if (slave->id == clientids[j]) { if (slave->id == clientids[j]) {
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START; slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
break; break;
@ -2577,7 +2577,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
updateDictResizePolicy(); updateDictResizePolicy();
} }
zfree(clientids); zfree(clientids);
zfree(fds); zfree(conns);
return (childpid == -1) ? C_ERR : C_OK; return (childpid == -1) ? C_ERR : C_OK;
} }
return C_OK; /* Unreached. */ return C_OK; /* Unreached. */

View File

@ -47,6 +47,9 @@
#include <math.h> #include <math.h>
#include <hiredis.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 <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
#include "dict.h" #include "dict.h"
#include "adlist.h" #include "adlist.h"
@ -188,6 +191,11 @@ static struct config {
char *hostip; char *hostip;
int hostport; int hostport;
char *hostsocket; char *hostsocket;
int tls;
char *sni;
char *cacert;
char *cert;
char *key;
long repeat; long repeat;
long interval; long interval;
int dbnum; int dbnum;
@ -751,6 +759,18 @@ static int cliSelect(void) {
return REDIS_ERR; return REDIS_ERR;
} }
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
* not building with TLS support.
*/
static int cliSecureConnection(redisContext *c) {
#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: /* 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 * CC_FORCE: The connection is performed even if there is already
* a connected socket. * a connected socket.
@ -767,6 +787,16 @@ static int cliConnect(int flags) {
context = redisConnectUnix(config.hostsocket); 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 (context->err) {
if (!(flags & CC_QUIET)) { if (!(flags & CC_QUIET)) {
fprintf(stderr,"Could not connect to Redis at "); fprintf(stderr,"Could not connect to Redis at ");
@ -782,6 +812,7 @@ static int cliConnect(int flags) {
return REDIS_ERR; return REDIS_ERR;
} }
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
* in order to prevent timeouts caused by the execution of long * in order to prevent timeouts caused by the execution of long
* commands. At the same time this improves the detection of real * commands. At the same time this improves the detection of real
@ -1245,6 +1276,9 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
redisFree(c); redisFree(c);
c = redisConnect(config.hostip,config.hostport); c = redisConnect(config.hostip,config.hostport);
if (!c->err && config.tls) {
cliSecureConnection(c);
}
usleep(1000000); usleep(1000000);
} }
@ -1434,6 +1468,18 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) { } else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
config.cluster_manager_command.flags |= config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS; CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
#ifdef USE_OPENSSL
} else if (!strcmp(argv[i],"--tls")) {
config.tls = 1;
} else if (!strcmp(argv[i],"--sni")) {
config.sni = argv[++i];
} else if (!strcmp(argv[i],"--cacert")) {
config.cacert = argv[++i];
} else if (!strcmp(argv[i],"--cert")) {
config.cert = argv[++i];
} else if (!strcmp(argv[i],"--key")) {
config.key = argv[++i];
#endif
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
sds version = cliVersion(); sds version = cliVersion();
printf("redis-cli %s\n", version); printf("redis-cli %s\n", version);
@ -1522,6 +1568,12 @@ static void usage(void) {
" -x Read last argument from STDIN.\n" " -x Read last argument from STDIN.\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n" " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
#ifdef USE_OPENSSL
" --tls Establish a secure TLS connection.\n"
" --cacert CA Certificate file to verify with.\n"
" --cert Client certificate to authenticate with.\n"
" --key Private key file to authenticate with.\n"
#endif
" --raw Use raw formatting for replies (default when STDOUT is\n" " --raw Use raw formatting for replies (default when STDOUT is\n"
" not a tty).\n" " not a tty).\n"
" --no-raw Force formatted output even when STDOUT is not a tty.\n" " --no-raw Force formatted output even when STDOUT is not a tty.\n"
@ -1544,7 +1596,9 @@ static void usage(void) {
" --pipe Transfer raw Redis protocol from stdin to server.\n" " --pipe Transfer raw Redis protocol from stdin to server.\n"
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n" " --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
" no reply is received within <n> seconds.\n" " no reply is received within <n> seconds.\n"
" Default timeout: %d. Use 0 to wait forever.\n" " Default timeout: %d. Use 0 to wait forever.\n",
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
fprintf(stderr,
" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n" " --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n" " --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n" " --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
@ -1567,8 +1621,7 @@ static void usage(void) {
" line interface.\n" " line interface.\n"
" --help Output this help and exit.\n" " --help Output this help and exit.\n"
" --version Output version and exit.\n" " --version Output version and exit.\n"
"\n", "\n");
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
/* Using another fprintf call to avoid -Woverlength-strings compile warning */ /* Using another fprintf call to avoid -Woverlength-strings compile warning */
fprintf(stderr, fprintf(stderr,
"Cluster Manager Commands:\n" "Cluster Manager Commands:\n"
@ -2336,6 +2389,9 @@ cleanup:
static int clusterManagerNodeConnect(clusterManagerNode *node) { static int clusterManagerNodeConnect(clusterManagerNode *node) {
if (node->context) redisFree(node->context); if (node->context) redisFree(node->context);
node->context = redisConnect(node->ip, node->port); node->context = redisConnect(node->ip, node->port);
if (!node->context->err && config.tls) {
cliSecureConnection(node->context);
}
if (node->context->err) { if (node->context->err) {
fprintf(stderr,"Could not connect to Redis at "); fprintf(stderr,"Could not connect to Redis at ");
fprintf(stderr,"%s:%d: %s\n", node->ip, node->port, fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,

View File

@ -39,7 +39,7 @@
#include <sys/stat.h> #include <sys/stat.h>
void replicationDiscardCachedMaster(void); void replicationDiscardCachedMaster(void);
void replicationResurrectCachedMaster(int newfd); void replicationResurrectCachedMaster(connection *conn);
void replicationSendAck(void); void replicationSendAck(void);
void putSlaveOnline(client *slave); void putSlaveOnline(client *slave);
int cancelReplicationHandshake(void); int cancelReplicationHandshake(void);
@ -57,7 +57,7 @@ char *replicationGetSlaveName(client *c) {
ip[0] = '\0'; ip[0] = '\0';
buf[0] = '\0'; buf[0] = '\0';
if (c->slave_ip[0] != '\0' || if (c->slave_ip[0] != '\0' ||
anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) connPeerToString(c->conn,ip,sizeof(ip),NULL) != -1)
{ {
/* Note that the 'ip' buffer is always larger than 'c->slave_ip' */ /* Note that the 'ip' buffer is always larger than 'c->slave_ip' */
if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip)); if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip));
@ -432,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
if (!(slave->flags & CLIENT_PRE_PSYNC)) { if (!(slave->flags & CLIENT_PRE_PSYNC)) {
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n", buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
server.replid,offset); server.replid,offset);
if (write(slave->fd,buf,buflen) != buflen) { if (connWrite(slave->conn,buf,buflen) != buflen) {
freeClientAsync(slave); freeClientAsync(slave);
return C_ERR; return C_ERR;
} }
@ -519,7 +519,7 @@ int masterTryPartialResynchronization(client *c) {
} else { } else {
buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n"); buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
} }
if (write(c->fd,buf,buflen) != buflen) { if (connWrite(c->conn,buf,buflen) != buflen) {
freeClientAsync(c); freeClientAsync(c);
return C_OK; return C_OK;
} }
@ -685,7 +685,7 @@ void syncCommand(client *c) {
* paths will change the state if we handle the slave differently. */ * paths will change the state if we handle the slave differently. */
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START; c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
if (server.repl_disable_tcp_nodelay) if (server.repl_disable_tcp_nodelay)
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */ connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */
c->repldbfd = -1; c->repldbfd = -1;
c->flags |= CLIENT_SLAVE; c->flags |= CLIENT_SLAVE;
listAddNodeTail(server.slaves,c); listAddNodeTail(server.slaves,c);
@ -862,8 +862,7 @@ void putSlaveOnline(client *slave) {
slave->replstate = SLAVE_STATE_ONLINE; slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 0; slave->repl_put_online_on_ack = 0;
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */ slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, if (connSetWriteHandler(slave->conn, sendReplyToClient) == C_ERR) {
sendReplyToClient, slave) == AE_ERR) {
serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno)); serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
freeClient(slave); freeClient(slave);
return; return;
@ -873,10 +872,8 @@ void putSlaveOnline(client *slave) {
replicationGetSlaveName(slave)); replicationGetSlaveName(slave));
} }
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { void sendBulkToSlave(connection *conn) {
client *slave = privdata; client *slave = connGetPrivateData(conn);
UNUSED(el);
UNUSED(mask);
char buf[PROTO_IOBUF_LEN]; char buf[PROTO_IOBUF_LEN];
ssize_t nwritten, buflen; ssize_t nwritten, buflen;
@ -884,7 +881,7 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
* replication process. Currently the preamble is just the bulk count of * replication process. Currently the preamble is just the bulk count of
* the file in the form "$<length>\r\n". */ * the file in the form "$<length>\r\n". */
if (slave->replpreamble) { if (slave->replpreamble) {
nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble)); nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
if (nwritten == -1) { if (nwritten == -1) {
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s", serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
strerror(errno)); strerror(errno));
@ -911,8 +908,8 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
freeClient(slave); freeClient(slave);
return; return;
} }
if ((nwritten = write(fd,buf,buflen)) == -1) { if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
if (errno != EAGAIN) { if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Write error sending DB to replica: %s", serverLog(LL_WARNING,"Write error sending DB to replica: %s",
strerror(errno)); strerror(errno));
freeClient(slave); freeClient(slave);
@ -924,7 +921,7 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
if (slave->repldboff == slave->repldbsize) { if (slave->repldboff == slave->repldbsize) {
close(slave->repldbfd); close(slave->repldbfd);
slave->repldbfd = -1; slave->repldbfd = -1;
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE); connSetWriteHandler(slave->conn,NULL);
putSlaveOnline(slave); putSlaveOnline(slave);
} }
} }
@ -1015,8 +1012,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n", slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
(unsigned long long) slave->repldbsize); (unsigned long long) slave->repldbsize);
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE); connSetWriteHandler(slave->conn,NULL);
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) { if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) {
freeClient(slave); freeClient(slave);
continue; continue;
} }
@ -1084,9 +1081,8 @@ void replicationSendNewlineToMaster(void) {
static time_t newline_sent; static time_t newline_sent;
if (time(NULL) != newline_sent) { if (time(NULL) != newline_sent) {
newline_sent = time(NULL); newline_sent = time(NULL);
if (write(server.repl_transfer_s,"\n",1) == -1) { /* Pinging back in this stage is best-effort. */
/* Pinging back in this stage is best-effort. */ if (server.repl_transfer_s) connWrite(server.repl_transfer_s, "\n", 1);
}
} }
} }
@ -1100,8 +1096,10 @@ void replicationEmptyDbCallback(void *privdata) {
/* Once we have a link with the master and the synchroniziation was /* Once we have a link with the master and the synchroniziation was
* performed, this function materializes the master client we store * performed, this function materializes the master client we store
* at server.master, starting from the specified file descriptor. */ * at server.master, starting from the specified file descriptor. */
void replicationCreateMasterClient(int fd, int dbid) { void replicationCreateMasterClient(connection *conn, int dbid) {
server.master = createClient(fd); server.master = createClient(conn);
if (conn)
connSetReadHandler(server.master->conn, readQueryFromClient);
server.master->flags |= CLIENT_MASTER; server.master->flags |= CLIENT_MASTER;
server.master->authenticated = 1; server.master->authenticated = 1;
server.master->reploff = server.master_initial_offset; server.master->reploff = server.master_initial_offset;
@ -1189,7 +1187,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
/* Asynchronously read the SYNC payload we receive from a master */ /* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { void readSyncBulkPayload(connection *conn) {
char buf[4096]; char buf[4096];
ssize_t nread, readlen, nwritten; ssize_t nread, readlen, nwritten;
int use_diskless_load; int use_diskless_load;
@ -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 : int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
EMPTYDB_NO_FLAGS; EMPTYDB_NO_FLAGS;
off_t left; off_t left;
UNUSED(el);
UNUSED(privdata);
UNUSED(mask);
/* Static vars used to hold the EOF mark, and the last bytes received /* Static vars used to hold the EOF mark, and the last bytes received
* form the server: when they match, we reached the end of the transfer. */ * form the server: when they match, we reached the end of the transfer. */
@ -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 /* If repl_transfer_size == -1 we still have to read the bulk length
* from the master reply. */ * from the master reply. */
if (server.repl_transfer_size == -1) { if (server.repl_transfer_size == -1) {
if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) { if (connSyncReadLine(conn,buf,1024,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"I/O error reading bulk count from MASTER: %s", "I/O error reading bulk count from MASTER: %s",
strerror(errno)); strerror(errno));
@ -1275,7 +1270,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf); readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
} }
nread = read(fd,buf,readlen); nread = connRead(conn,buf,readlen);
if (nread <= 0) { if (nread <= 0) {
serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s", serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
(nread == -1) ? strerror(errno) : "connection lost"); (nread == -1) ? strerror(errno) : "connection lost");
@ -1383,17 +1378,17 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
* handler, otherwise it will get called recursively since * handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to * rdbLoad() will call the event loop to process events from time to
* time for non blocking loading. */ * time for non blocking loading. */
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE); connSetReadHandler(conn, NULL);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory"); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (use_diskless_load) { if (use_diskless_load) {
rio rdb; rio rdb;
rioInitWithFd(&rdb,fd,server.repl_transfer_size); rioInitWithConn(&rdb,conn,server.repl_transfer_size);
/* Put the socket in blocking mode to simplify RDB transfer. /* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */ * We'll restore it when the RDB is received. */
anetBlock(NULL,fd); connBlock(conn);
anetRecvTimeout(NULL,fd,server.repl_timeout*1000); connRecvTimeout(conn, server.repl_timeout*1000);
startLoading(server.repl_transfer_size); startLoading(server.repl_transfer_size);
if (rdbLoadRio(&rdb,&rsi,0) != C_OK) { if (rdbLoadRio(&rdb,&rsi,0) != C_OK) {
@ -1403,7 +1398,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
"Failed trying to load the MASTER synchronization DB " "Failed trying to load the MASTER synchronization DB "
"from socket"); "from socket");
cancelReplicationHandshake(); cancelReplicationHandshake();
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) { if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
/* Restore the backed up databases. */ /* Restore the backed up databases. */
disklessLoadRestoreBackups(diskless_load_backup,1, disklessLoadRestoreBackups(diskless_load_backup,1,
@ -1436,16 +1431,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
{ {
serverLog(LL_WARNING,"Replication stream EOF marker is broken"); serverLog(LL_WARNING,"Replication stream EOF marker is broken");
cancelReplicationHandshake(); cancelReplicationHandshake();
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
return; return;
} }
} }
/* Cleanup and restore the socket to the original state to continue /* Cleanup and restore the socket to the original state to continue
* with the normal replication. */ * with the normal replication. */
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
anetNonBlock(NULL,fd); connNonBlock(conn);
anetRecvTimeout(NULL,fd,0); connRecvTimeout(conn,0);
} else { } else {
/* Ensure background save doesn't overwrite synced data */ /* Ensure background save doesn't overwrite synced data */
if (server.rdb_child_pid != -1) { if (server.rdb_child_pid != -1) {
@ -1522,7 +1517,7 @@ error:
#define SYNC_CMD_READ (1<<0) #define SYNC_CMD_READ (1<<0)
#define SYNC_CMD_WRITE (1<<1) #define SYNC_CMD_WRITE (1<<1)
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE) #define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
char *sendSynchronousCommand(int flags, int fd, ...) { char *sendSynchronousCommand(int flags, connection *conn, ...) {
/* Create the command to send to the master, we use redis binary /* Create the command to send to the master, we use redis binary
* protocol to make sure correct arguments are sent. This function * protocol to make sure correct arguments are sent. This function
@ -1533,7 +1528,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sds cmd = sdsempty(); sds cmd = sdsempty();
sds cmdargs = sdsempty(); sds cmdargs = sdsempty();
size_t argslen = 0; size_t argslen = 0;
va_start(ap,fd); va_start(ap,conn);
while(1) { while(1) {
arg = va_arg(ap, char*); arg = va_arg(ap, char*);
@ -1550,12 +1545,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sdsfree(cmdargs); sdsfree(cmdargs);
/* Transfer command to the server. */ /* Transfer command to the server. */
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
== -1) == -1)
{ {
sdsfree(cmd); sdsfree(cmd);
return sdscatprintf(sdsempty(),"-Writing to master: %s", return sdscatprintf(sdsempty(),"-Writing to master: %s",
strerror(errno)); connGetLastError(conn));
} }
sdsfree(cmd); sdsfree(cmd);
} }
@ -1564,7 +1559,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
if (flags & SYNC_CMD_READ) { if (flags & SYNC_CMD_READ) {
char buf[256]; char buf[256];
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000) if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000)
== -1) == -1)
{ {
return sdscatprintf(sdsempty(),"-Reading from master: %s", return sdscatprintf(sdsempty(),"-Reading from master: %s",
@ -1630,7 +1625,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
#define PSYNC_FULLRESYNC 3 #define PSYNC_FULLRESYNC 3
#define PSYNC_NOT_SUPPORTED 4 #define PSYNC_NOT_SUPPORTED 4
#define PSYNC_TRY_LATER 5 #define PSYNC_TRY_LATER 5
int slaveTryPartialResynchronization(int fd, int read_reply) { int slaveTryPartialResynchronization(connection *conn, int read_reply) {
char *psync_replid; char *psync_replid;
char psync_offset[32]; char psync_offset[32];
sds reply; sds reply;
@ -1655,18 +1650,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
} }
/* Issue the PSYNC command */ /* Issue the PSYNC command */
reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_replid,psync_offset,NULL); reply = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PSYNC",psync_replid,psync_offset,NULL);
if (reply != NULL) { if (reply != NULL) {
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply); serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
sdsfree(reply); sdsfree(reply);
aeDeleteFileEvent(server.el,fd,AE_READABLE); connSetReadHandler(conn, NULL);
return PSYNC_WRITE_ERROR; return PSYNC_WRITE_ERROR;
} }
return PSYNC_WAIT_REPLY; return PSYNC_WAIT_REPLY;
} }
/* Reading half */ /* Reading half */
reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); reply = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (sdslen(reply) == 0) { if (sdslen(reply) == 0) {
/* The master may send empty newlines after it receives PSYNC /* The master may send empty newlines after it receives PSYNC
* and before to reply, just to keep the connection alive. */ * and before to reply, just to keep the connection alive. */
@ -1674,7 +1669,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
return PSYNC_WAIT_REPLY; return PSYNC_WAIT_REPLY;
} }
aeDeleteFileEvent(server.el,fd,AE_READABLE); connSetReadHandler(conn, NULL);
if (!strncmp(reply,"+FULLRESYNC",11)) { if (!strncmp(reply,"+FULLRESYNC",11)) {
char *replid = NULL, *offset = NULL; char *replid = NULL, *offset = NULL;
@ -1748,7 +1743,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* Setup the replication to continue. */ /* Setup the replication to continue. */
sdsfree(reply); sdsfree(reply);
replicationResurrectCachedMaster(fd); replicationResurrectCachedMaster(conn);
/* If this instance was restarted and we read the metadata to /* If this instance was restarted and we read the metadata to
* PSYNC from the persistence file, our replication backlog could * PSYNC from the persistence file, our replication backlog could
@ -1790,29 +1785,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* This handler fires when the non blocking connect was able to /* This handler fires when the non blocking connect was able to
* establish a connection with the master. */ * establish a connection with the master. */
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { void syncWithMaster(connection *conn) {
char tmpfile[256], *err = NULL; char tmpfile[256], *err = NULL;
int dfd = -1, maxtries = 5; int dfd = -1, maxtries = 5;
int sockerr = 0, psync_result; int psync_result;
socklen_t errlen = sizeof(sockerr);
UNUSED(el);
UNUSED(privdata);
UNUSED(mask);
/* If this event fired after the user turned the instance into a master /* If this event fired after the user turned the instance into a master
* with SLAVEOF NO ONE we must just return ASAP. */ * with SLAVEOF NO ONE we must just return ASAP. */
if (server.repl_state == REPL_STATE_NONE) { if (server.repl_state == REPL_STATE_NONE) {
close(fd); connClose(conn);
return; return;
} }
/* Check for errors in the socket: after a non blocking connect() we /* Check for errors in the socket: after a non blocking connect() we
* may find that the socket is in error state. */ * may find that the socket is in error state. */
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1) if (connGetState(conn) != CONN_STATE_CONNECTED) {
sockerr = errno;
if (sockerr) {
serverLog(LL_WARNING,"Error condition on socket for SYNC: %s", serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
strerror(sockerr)); connGetLastError(conn));
goto error; goto error;
} }
@ -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."); serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
/* Delete the writable event so that the readable event remains /* Delete the writable event so that the readable event remains
* registered and we can wait for the PONG reply. */ * registered and we can wait for the PONG reply. */
aeDeleteFileEvent(server.el,fd,AE_WRITABLE); connSetReadHandler(conn, syncWithMaster);
connSetWriteHandler(conn, NULL);
server.repl_state = REPL_STATE_RECEIVE_PONG; server.repl_state = REPL_STATE_RECEIVE_PONG;
/* Send the PING, don't check for errors at all, we have the timeout /* Send the PING, don't check for errors at all, we have the timeout
* that will take care about this. */ * that will take care about this. */
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL); err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
if (err) goto write_error; if (err) goto write_error;
return; return;
} }
/* Receive the PONG command. */ /* Receive the PONG command. */
if (server.repl_state == REPL_STATE_RECEIVE_PONG) { if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* We accept only two replies as valid, a positive +PONG reply /* We accept only two replies as valid, a positive +PONG reply
* (we just check for "+") or an authentication error. * (we just check for "+") or an authentication error.
@ -1857,13 +1847,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* AUTH with the master if required. */ /* AUTH with the master if required. */
if (server.repl_state == REPL_STATE_SEND_AUTH) { if (server.repl_state == REPL_STATE_SEND_AUTH) {
if (server.masteruser && server.masterauth) { if (server.masteruser && server.masterauth) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",
server.masteruser,server.masterauth,NULL); server.masteruser,server.masterauth,NULL);
if (err) goto write_error; if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH; server.repl_state = REPL_STATE_RECEIVE_AUTH;
return; return;
} else if (server.masterauth) { } else if (server.masterauth) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL); err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",server.masterauth,NULL);
if (err) goto write_error; if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH; server.repl_state = REPL_STATE_RECEIVE_AUTH;
return; return;
@ -1874,7 +1864,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive AUTH reply. */ /* Receive AUTH reply. */
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) { if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (err[0] == '-') { if (err[0] == '-') {
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err); serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
sdsfree(err); sdsfree(err);
@ -1889,7 +1879,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
if (server.repl_state == REPL_STATE_SEND_PORT) { if (server.repl_state == REPL_STATE_SEND_PORT) {
sds port = sdsfromlonglong(server.slave_announce_port ? sds port = sdsfromlonglong(server.slave_announce_port ?
server.slave_announce_port : server.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); "listening-port",port, NULL);
sdsfree(port); sdsfree(port);
if (err) goto write_error; if (err) goto write_error;
@ -1900,7 +1890,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF listening-port reply. */ /* Receive REPLCONF listening-port reply. */
if (server.repl_state == REPL_STATE_RECEIVE_PORT) { if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */ * REPLCONF listening-port. */
if (err[0] == '-') { if (err[0] == '-') {
@ -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 /* Set the slave ip, so that Master's INFO command can list the
* slave IP address port correctly in case of port forwarding or NAT. */ * slave IP address port correctly in case of port forwarding or NAT. */
if (server.repl_state == REPL_STATE_SEND_IP) { if (server.repl_state == REPL_STATE_SEND_IP) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"ip-address",server.slave_announce_ip, NULL); "ip-address",server.slave_announce_ip, NULL);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
@ -1931,7 +1921,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF ip-address reply. */ /* Receive REPLCONF ip-address reply. */
if (server.repl_state == REPL_STATE_RECEIVE_IP) { if (server.repl_state == REPL_STATE_RECEIVE_IP) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */ * REPLCONF listening-port. */
if (err[0] == '-') { if (err[0] == '-') {
@ -1949,7 +1939,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* *
* The master will ignore capabilities it does not understand. */ * The master will ignore capabilities it does not understand. */
if (server.repl_state == REPL_STATE_SEND_CAPA) { if (server.repl_state == REPL_STATE_SEND_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"capa","eof","capa","psync2",NULL); "capa","eof","capa","psync2",NULL);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
@ -1959,7 +1949,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive CAPA reply. */ /* Receive CAPA reply. */
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) { if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF capa. */ * REPLCONF capa. */
if (err[0] == '-') { if (err[0] == '-') {
@ -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 * and the global offset, to try a partial resync at the next
* reconnection attempt. */ * reconnection attempt. */
if (server.repl_state == REPL_STATE_SEND_PSYNC) { if (server.repl_state == REPL_STATE_SEND_PSYNC) {
if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) { if (slaveTryPartialResynchronization(conn,0) == PSYNC_WRITE_ERROR) {
err = sdsnew("Write error sending the PSYNC command."); err = sdsnew("Write error sending the PSYNC command.");
goto write_error; goto write_error;
} }
@ -1992,7 +1982,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
goto error; goto error;
} }
psync_result = slaveTryPartialResynchronization(fd,1); psync_result = slaveTryPartialResynchronization(conn,1);
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */ if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
/* If the master is in an transient error, we should try to PSYNC /* If the master is in an transient error, we should try to PSYNC
@ -2021,7 +2011,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* already populated. */ * already populated. */
if (psync_result == PSYNC_NOT_SUPPORTED) { if (psync_result == PSYNC_NOT_SUPPORTED) {
serverLog(LL_NOTICE,"Retrying with SYNC..."); serverLog(LL_NOTICE,"Retrying with SYNC...");
if (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) { if (connSyncWrite(conn,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,"I/O error writing to MASTER: %s", serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
strerror(errno)); strerror(errno));
goto error; goto error;
@ -2046,12 +2036,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
/* Setup the non blocking download of the bulk file. */ /* Setup the non blocking download of the bulk file. */
if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL) if (connSetReadHandler(conn, readSyncBulkPayload)
== AE_ERR) == C_ERR)
{ {
char conninfo[CONN_INFO_LEN];
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Can't create readable event for SYNC: %s (fd=%d)", "Can't create readable event for SYNC: %s (%s)",
strerror(errno),fd); strerror(errno), connGetInfo(conn, conninfo, sizeof(conninfo)));
goto error; goto error;
} }
@ -2063,16 +2054,15 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
error: error:
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
if (dfd != -1) close(dfd); if (dfd != -1) close(dfd);
close(fd); connClose(conn);
server.repl_transfer_s = NULL;
if (server.repl_transfer_fd != -1) if (server.repl_transfer_fd != -1)
close(server.repl_transfer_fd); close(server.repl_transfer_fd);
if (server.repl_transfer_tmpfile) if (server.repl_transfer_tmpfile)
zfree(server.repl_transfer_tmpfile); zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL; server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1; server.repl_transfer_fd = -1;
server.repl_transfer_s = -1;
server.repl_state = REPL_STATE_CONNECT; server.repl_state = REPL_STATE_CONNECT;
return; return;
@ -2083,26 +2073,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
} }
int connectWithMaster(void) { int connectWithMaster(void) {
int fd; server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
fd = anetTcpNonBlockBestEffortBindConnect(NULL, NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
server.masterhost,server.masterport,NET_FIRST_BIND_ADDR);
if (fd == -1) {
serverLog(LL_WARNING,"Unable to connect to MASTER: %s", serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
strerror(errno)); connGetLastError(server.repl_transfer_s));
connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
return C_ERR; return C_ERR;
} }
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
AE_ERR)
{
close(fd);
serverLog(LL_WARNING,"Can't create readable event for SYNC");
return C_ERR;
}
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_s = fd;
server.repl_state = REPL_STATE_CONNECTING; server.repl_state = REPL_STATE_CONNECTING;
return C_OK; return C_OK;
} }
@ -2112,11 +2094,8 @@ int connectWithMaster(void) {
* Never call this function directly, use cancelReplicationHandshake() instead. * Never call this function directly, use cancelReplicationHandshake() instead.
*/ */
void undoConnectWithMaster(void) { void undoConnectWithMaster(void) {
int fd = server.repl_transfer_s; connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
close(fd);
server.repl_transfer_s = -1;
} }
/* Abort the async download of the bulk dataset while SYNC-ing with master. /* Abort the async download of the bulk dataset while SYNC-ing with master.
@ -2301,7 +2280,7 @@ void roleCommand(client *c) {
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip; char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
if (slaveip[0] == '\0') { if (slaveip[0] == '\0') {
if (anetPeerToString(slave->fd,ip,sizeof(ip),NULL) == -1) if (connPeerToString(slave->conn,ip,sizeof(ip),NULL) == -1)
continue; continue;
slaveip = ip; slaveip = ip;
} }
@ -2423,7 +2402,7 @@ void replicationCacheMasterUsingMyself(void) {
/* The master client we create can be set to any DBID, because /* The master client we create can be set to any DBID, because
* the new master will start its replication stream with SELECT. */ * the new master will start its replication stream with SELECT. */
server.master_initial_offset = server.master_repl_offset; server.master_initial_offset = server.master_repl_offset;
replicationCreateMasterClient(-1,-1); replicationCreateMasterClient(NULL,-1);
/* Use our own ID / offset. */ /* Use our own ID / offset. */
memcpy(server.master->replid, server.replid, sizeof(server.replid)); memcpy(server.master->replid, server.replid, sizeof(server.replid));
@ -2452,10 +2431,11 @@ void replicationDiscardCachedMaster(void) {
* This function is called when successfully setup a partial resynchronization * This function is called when successfully setup a partial resynchronization
* so the stream of data that we'll receive will start from were this * so the stream of data that we'll receive will start from were this
* master left. */ * master left. */
void replicationResurrectCachedMaster(int newfd) { void replicationResurrectCachedMaster(connection *conn) {
server.master = server.cached_master; server.master = server.cached_master;
server.cached_master = NULL; server.cached_master = NULL;
server.master->fd = newfd; server.master->conn = conn;
connSetPrivateData(server.master->conn, server.master);
server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP); server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
server.master->authenticated = 1; server.master->authenticated = 1;
server.master->lastinteraction = server.unixtime; server.master->lastinteraction = server.unixtime;
@ -2464,8 +2444,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* Re-add to the list of clients. */ /* Re-add to the list of clients. */
linkClient(server.master); linkClient(server.master);
if (aeCreateFileEvent(server.el, newfd, AE_READABLE, if (connSetReadHandler(server.master->conn, readQueryFromClient)) {
readQueryFromClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno)); serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */ freeClientAsync(server.master); /* Close ASAP. */
} }
@ -2473,8 +2452,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* We may also need to install the write handler as well if there is /* We may also need to install the write handler as well if there is
* pending data in the write buffers. */ * pending data in the write buffers. */
if (clientHasPendingReplies(server.master)) { if (clientHasPendingReplies(server.master)) {
if (aeCreateFileEvent(server.el, newfd, AE_WRITABLE, if (connSetWriteHandler(server.master->conn, sendReplyToClient)) {
sendReplyToClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno)); serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */ freeClientAsync(server.master); /* Close ASAP. */
} }
@ -2844,9 +2822,7 @@ void replicationCron(void) {
server.rdb_child_type != RDB_CHILD_TYPE_SOCKET)); server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));
if (is_presync) { if (is_presync) {
if (write(slave->fd, "\n", 1) == -1) { connWrite(slave->conn, "\n", 1);
/* Don't worry about socket errors, it's just a ping. */
}
} }
} }

144
src/rio.c
View File

@ -159,13 +159,13 @@ void rioInitWithFile(rio *r, FILE *fp) {
r->io.file.autosync = 0; r->io.file.autosync = 0;
} }
/* ------------------- File descriptor implementation ------------------- /* ------------------- Connection implementation -------------------
* We use this RIO implemetnation when reading an RDB file directly from * We use this RIO implemetnation when reading an RDB file directly from
* the socket to the memory via rdbLoadRio(), thus this implementation * the connection to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a file descriptor that is, normally, * only implements reading from a connection that is, normally,
* just a socket. */ * just a socket. */
static size_t rioFdWrite(rio *r, const void *buf, size_t len) { static size_t rioConnWrite(rio *r, const void *buf, size_t len) {
UNUSED(r); UNUSED(r);
UNUSED(buf); UNUSED(buf);
UNUSED(len); UNUSED(len);
@ -173,72 +173,72 @@ static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
} }
/* Returns 1 or 0 for success/failure. */ /* Returns 1 or 0 for success/failure. */
static size_t rioFdRead(rio *r, void *buf, size_t len) { static size_t rioConnRead(rio *r, void *buf, size_t len) {
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos; size_t avail = sdslen(r->io.conn.buf)-r->io.conn.pos;
/* If the buffer is too small for the entire request: realloc. */ /* If the buffer is too small for the entire request: realloc. */
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len) if (sdslen(r->io.conn.buf) + sdsavail(r->io.conn.buf) < len)
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf)); r->io.conn.buf = sdsMakeRoomFor(r->io.conn.buf, len - sdslen(r->io.conn.buf));
/* If the remaining unused buffer is not large enough: memmove so that we /* If the remaining unused buffer is not large enough: memmove so that we
* can read the rest. */ * can read the rest. */
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) { if (len > avail && sdsavail(r->io.conn.buf) < len - avail) {
sdsrange(r->io.fd.buf, r->io.fd.pos, -1); sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
r->io.fd.pos = 0; r->io.conn.pos = 0;
} }
/* If we don't already have all the data in the sds, read more */ /* If we don't already have all the data in the sds, read more */
while (len > sdslen(r->io.fd.buf) - r->io.fd.pos) { while (len > sdslen(r->io.conn.buf) - r->io.conn.pos) {
size_t buffered = sdslen(r->io.fd.buf) - r->io.fd.pos; size_t buffered = sdslen(r->io.conn.buf) - r->io.conn.pos;
size_t toread = len - buffered; size_t toread = len - buffered;
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of /* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
* the two. */ * the two. */
if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN; if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
if (toread > sdsavail(r->io.fd.buf)) toread = sdsavail(r->io.fd.buf); if (toread > sdsavail(r->io.conn.buf)) toread = sdsavail(r->io.conn.buf);
if (r->io.fd.read_limit != 0 && if (r->io.conn.read_limit != 0 &&
r->io.fd.read_so_far + buffered + toread > r->io.fd.read_limit) r->io.conn.read_so_far + buffered + toread > r->io.conn.read_limit)
{ {
if (r->io.fd.read_limit >= r->io.fd.read_so_far - buffered) if (r->io.conn.read_limit >= r->io.conn.read_so_far - buffered)
toread = r->io.fd.read_limit - r->io.fd.read_so_far - buffered; toread = r->io.conn.read_limit - r->io.conn.read_so_far - buffered;
else { else {
errno = EOVERFLOW; errno = EOVERFLOW;
return 0; return 0;
} }
} }
int retval = read(r->io.fd.fd, int retval = connRead(r->io.conn.conn,
(char*)r->io.fd.buf + sdslen(r->io.fd.buf), (char*)r->io.conn.buf + sdslen(r->io.conn.buf),
toread); toread);
if (retval <= 0) { if (retval <= 0) {
if (errno == EWOULDBLOCK) errno = ETIMEDOUT; if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
return 0; return 0;
} }
sdsIncrLen(r->io.fd.buf, retval); sdsIncrLen(r->io.conn.buf, retval);
} }
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len); memcpy(buf, (char*)r->io.conn.buf + r->io.conn.pos, len);
r->io.fd.read_so_far += len; r->io.conn.read_so_far += len;
r->io.fd.pos += len; r->io.conn.pos += len;
return len; return len;
} }
/* Returns read/write position in file. */ /* Returns read/write position in file. */
static off_t rioFdTell(rio *r) { static off_t rioConnTell(rio *r) {
return r->io.fd.read_so_far; return r->io.conn.read_so_far;
} }
/* Flushes any buffer to target device if applicable. Returns 1 on success /* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */ * and 0 on failures. */
static int rioFdFlush(rio *r) { static int rioConnFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a /* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */ * buffer set to NULL with a count of zero as a flush request. */
return rioFdWrite(r,NULL,0); return rioConnWrite(r,NULL,0);
} }
static const rio rioFdIO = { static const rio rioConnIO = {
rioFdRead, rioConnRead,
rioFdWrite, rioConnWrite,
rioFdTell, rioConnTell,
rioFdFlush, rioConnFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */ 0, /* flags */
@ -249,27 +249,27 @@ static const rio rioFdIO = {
/* Create an RIO that implements a buffered read from an fd /* Create an RIO that implements a buffered read from an fd
* read_limit argument stops buffering when the reaching the limit. */ * read_limit argument stops buffering when the reaching the limit. */
void rioInitWithFd(rio *r, int fd, size_t read_limit) { void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
*r = rioFdIO; *r = rioConnIO;
r->io.fd.fd = fd; r->io.conn.conn = conn;
r->io.fd.pos = 0; r->io.conn.pos = 0;
r->io.fd.read_limit = read_limit; r->io.conn.read_limit = read_limit;
r->io.fd.read_so_far = 0; r->io.conn.read_so_far = 0;
r->io.fd.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN); r->io.conn.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
sdsclear(r->io.fd.buf); sdsclear(r->io.conn.buf);
} }
/* Release the RIO tream. Optionally returns the unread buffered data /* Release the RIO tream. Optionally returns the unread buffered data
* when the SDS pointer 'remaining' is passed. */ * when the SDS pointer 'remaining' is passed. */
void rioFreeFd(rio *r, sds *remaining) { void rioFreeConn(rio *r, sds *remaining) {
if (remaining && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) { if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {
if (r->io.fd.pos > 0) sdsrange(r->io.fd.buf, r->io.fd.pos, -1); if (r->io.conn.pos > 0) sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
*remaining = r->io.fd.buf; *remaining = r->io.conn.buf;
} else { } else {
sdsfree(r->io.fd.buf); sdsfree(r->io.conn.buf);
if (remaining) *remaining = NULL; if (remaining) *remaining = NULL;
} }
r->io.fd.buf = NULL; r->io.conn.buf = NULL;
} }
/* ------------------- File descriptors set implementation ------------------ /* ------------------- File 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 /* To start we always append to our buffer. If it gets larger than
* a given size, we actually write to the sockets. */ * a given size, we actually write to the sockets. */
if (len) { 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. */ 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) { if (doflush) {
p = (unsigned char*) r->io.fdset.buf; p = (unsigned char*) r->io.connset.buf;
len = sdslen(r->io.fdset.buf); len = sdslen(r->io.connset.buf);
} }
/* Write in little chunchs so that when there are big writes we /* 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) { while(len) {
size_t count = len < 1024 ? len : 1024; size_t count = len < 1024 ? len : 1024;
int broken = 0; int broken = 0;
for (j = 0; j < r->io.fdset.numfds; j++) { for (j = 0; j < r->io.connset.numconns; j++) {
if (r->io.fdset.state[j] != 0) { if (r->io.connset.state[j] != 0) {
/* Skip FDs alraedy in error. */ /* Skip FDs alraedy in error. */
broken++; broken++;
continue; continue;
@ -321,7 +321,7 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
* of short writes. */ * of short writes. */
size_t nwritten = 0; size_t nwritten = 0;
while(nwritten != count) { 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) { if (retval <= 0) {
/* With blocking sockets, which is the sole user of this /* With blocking sockets, which is the sole user of this
* rio target, EWOULDBLOCK is returned only because of * 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) { if (nwritten != count) {
/* Mark this FD as broken. */ /* Mark this FD as broken. */
r->io.fdset.state[j] = errno; r->io.connset.state[j] = errno;
if (r->io.fdset.state[j] == 0) r->io.fdset.state[j] = EIO; 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; p += count;
len -= 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; return 1;
} }
@ -359,7 +359,7 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
/* Returns read/write position in file. */ /* Returns read/write position in file. */
static off_t rioFdsetTell(rio *r) { static off_t 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 /* 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 */ { { 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; int j;
*r = rioFdsetIO; *r = rioFdsetIO;
r->io.fdset.fds = zmalloc(sizeof(int)*numfds); r->io.connset.conns = zmalloc(sizeof(connection *)*numconns);
r->io.fdset.state = zmalloc(sizeof(int)*numfds); r->io.connset.state = zmalloc(sizeof(int)*numconns);
memcpy(r->io.fdset.fds,fds,sizeof(int)*numfds); memcpy(r->io.connset.conns,conns,sizeof(connection *)*numconns);
for (j = 0; j < numfds; j++) r->io.fdset.state[j] = 0; for (j = 0; j < numconns; j++) r->io.connset.state[j] = 0;
r->io.fdset.numfds = numfds; r->io.connset.numconns = numconns;
r->io.fdset.pos = 0; r->io.connset.pos = 0;
r->io.fdset.buf = sdsempty(); r->io.connset.buf = sdsempty();
} }
/* release the rio stream. */ /* release the rio stream. */
void rioFreeFdset(rio *r) { void rioFreeConnset(rio *r) {
zfree(r->io.fdset.fds); zfree(r->io.connset.conns);
zfree(r->io.fdset.state); zfree(r->io.connset.state);
sdsfree(r->io.fdset.buf); sdsfree(r->io.connset.buf);
} }
/* ---------------------------- Generic functions ---------------------------- */ /* ---------------------------- Generic functions ---------------------------- */

View File

@ -35,6 +35,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include "sds.h" #include "sds.h"
#include "connection.h"
#define RIO_FLAG_READ_ERROR (1<<0) #define RIO_FLAG_READ_ERROR (1<<0)
#define RIO_FLAG_WRITE_ERROR (1<<1) #define RIO_FLAG_WRITE_ERROR (1<<1)
@ -76,22 +77,22 @@ struct _rio {
off_t buffered; /* Bytes written since last fsync. */ off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */ off_t autosync; /* fsync after 'autosync' bytes written. */
} file; } file;
/* file descriptor */ /* Connection object */
struct { struct {
int fd; /* File descriptor. */ connection *conn; /* Connection */
off_t pos; /* pos in buf that was returned */ off_t pos; /* pos in buf that was returned */
sds buf; /* buffered data */ sds buf; /* buffered data */
size_t read_limit; /* don't allow to buffer/read more than that */ size_t read_limit; /* don't allow to buffer/read more than that */
size_t read_so_far; /* amount of data read from the rio (not buffered) */ size_t read_so_far; /* amount of data read from the rio (not buffered) */
} fd; } conn;
/* Multiple FDs target (used to write to N sockets). */ /* Multiple FDs target (used to write to N sockets). */
struct { struct {
int *fds; /* File descriptors. */ connection **conns; /* Connections */
int *state; /* Error state of each fd. 0 (if ok) or errno. */ int *state; /* Error state of each fd. 0 (if ok) or errno. */
int numfds; int numconns;
off_t pos; off_t pos;
sds buf; sds buf;
} fdset; } connset;
} io; } io;
}; };
@ -159,11 +160,11 @@ static inline void rioClearErrors(rio *r) {
void rioInitWithFile(rio *r, FILE *fp); void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s); void rioInitWithBuffer(rio *r, sds s);
void rioInitWithFd(rio *r, int fd, size_t read_limit); void rioInitWithConn(rio *r, connection *conn, size_t read_limit);
void rioInitWithFdset(rio *r, int *fds, int numfds); void rioInitWithConnset(rio *r, connection **conns, int numconns);
void rioFreeFdset(rio *r); void rioFreeConnset(rio *r);
void rioFreeFd(rio *r, sds* out_remainingBufferedData); void rioFreeConn(rio *r, sds* out_remainingBufferedData);
size_t rioWriteBulkCount(rio *r, char prefix, long count); size_t rioWriteBulkCount(rio *r, char prefix, long count);
size_t rioWriteBulkString(rio *r, const char *buf, size_t len); size_t rioWriteBulkString(rio *r, const char *buf, size_t len);

View File

@ -58,7 +58,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx);
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */ #define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */ #define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
struct ldbState { struct ldbState {
int fd; /* Socket of the debugging client. */ connection *conn; /* Connection of the debugging client. */
int active; /* Are we debugging EVAL right now? */ int active; /* Are we debugging EVAL right now? */
int forked; /* Is this a fork()ed debugging session? */ int forked; /* Is this a fork()ed debugging session? */
list *logs; /* List of messages to send to the client. */ list *logs; /* List of messages to send to the client. */
@ -1109,7 +1109,7 @@ void scriptingInit(int setup) {
* Note: there is no need to create it again when this function is called * Note: there is no need to create it again when this function is called
* by scriptingReset(). */ * by scriptingReset(). */
if (server.lua_client == NULL) { if (server.lua_client == NULL) {
server.lua_client = createClient(-1); server.lua_client = createClient(NULL);
server.lua_client->flags |= CLIENT_LUA; server.lua_client->flags |= CLIENT_LUA;
} }
@ -1593,7 +1593,7 @@ NULL
/* Initialize Lua debugger data structures. */ /* Initialize Lua debugger data structures. */
void ldbInit(void) { void ldbInit(void) {
ldb.fd = -1; ldb.conn = NULL;
ldb.active = 0; ldb.active = 0;
ldb.logs = listCreate(); ldb.logs = listCreate();
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree); listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
@ -1615,7 +1615,7 @@ void ldbFlushLog(list *log) {
void ldbEnable(client *c) { void ldbEnable(client *c) {
c->flags |= CLIENT_LUA_DEBUG; c->flags |= CLIENT_LUA_DEBUG;
ldbFlushLog(ldb.logs); ldbFlushLog(ldb.logs);
ldb.fd = c->fd; ldb.conn = c->conn;
ldb.step = 1; ldb.step = 1;
ldb.bpcount = 0; ldb.bpcount = 0;
ldb.luabp = 0; ldb.luabp = 0;
@ -1670,7 +1670,7 @@ void ldbSendLogs(void) {
proto = sdscatlen(proto,"\r\n",2); proto = sdscatlen(proto,"\r\n",2);
listDelNode(ldb.logs,ln); listDelNode(ldb.logs,ln);
} }
if (write(ldb.fd,proto,sdslen(proto)) == -1) { if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) {
/* Avoid warning. We don't check the return value of write() /* Avoid warning. We don't check the return value of write()
* since the next read() will catch the I/O error and will * since the next read() will catch the I/O error and will
* close the debugging session. */ * close the debugging session. */
@ -1723,8 +1723,8 @@ int ldbStartSession(client *c) {
} }
/* Setup our debugging session. */ /* Setup our debugging session. */
anetBlock(NULL,ldb.fd); connBlock(ldb.conn);
anetSendTimeout(NULL,ldb.fd,5000); connSendTimeout(ldb.conn,5000);
ldb.active = 1; ldb.active = 1;
/* First argument of EVAL is the script itself. We split it into different /* First argument of EVAL is the script itself. We split it into different
@ -1751,7 +1751,7 @@ void ldbEndSession(client *c) {
/* If it's a fork()ed session, we just exit. */ /* If it's a fork()ed session, we just exit. */
if (ldb.forked) { if (ldb.forked) {
writeToClient(c->fd, c, 0); writeToClient(c,0);
serverLog(LL_WARNING,"Lua debugging session child exiting"); serverLog(LL_WARNING,"Lua debugging session child exiting");
exitFromChild(0); exitFromChild(0);
} else { } else {
@ -1760,8 +1760,8 @@ void ldbEndSession(client *c) {
} }
/* Otherwise let's restore client's state. */ /* Otherwise let's restore client's state. */
anetNonBlock(NULL,ldb.fd); connNonBlock(ldb.conn);
anetSendTimeout(NULL,ldb.fd,0); connSendTimeout(ldb.conn,0);
/* Close the client connectin after sending the final EVAL reply /* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */ * in order to signal the end of the debugging session. */
@ -2332,7 +2332,7 @@ int ldbRepl(lua_State *lua) {
while(1) { while(1) {
while((argv = ldbReplParseCommand(&argc)) == NULL) { while((argv = ldbReplParseCommand(&argc)) == NULL) {
char buf[1024]; char buf[1024];
int nread = read(ldb.fd,buf,sizeof(buf)); int nread = connRead(ldb.conn,buf,sizeof(buf));
if (nread <= 0) { if (nread <= 0) {
/* Make sure the script runs without user input since the /* Make sure the script runs without user input since the
* client is no longer connected. */ * client is no longer connected. */

View File

@ -30,6 +30,10 @@
#include "server.h" #include "server.h"
#include "hiredis.h" #include "hiredis.h"
#ifdef USE_OPENSSL
#include "openssl/ssl.h"
#include "hiredis_ssl.h"
#endif
#include "async.h" #include "async.h"
#include <ctype.h> #include <ctype.h>
@ -40,6 +44,10 @@
extern char **environ; extern char **environ;
#ifdef USE_OPENSSL
extern SSL_CTX *redis_tls_ctx;
#endif
#define REDIS_SENTINEL_PORT 26379 #define REDIS_SENTINEL_PORT 26379
/* ======================== Sentinel global state =========================== */ /* ======================== Sentinel global state =========================== */
@ -1995,6 +2003,19 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
} }
} }
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 /* Create the async connections for the instance link if the link
* is disconnected. Note that link->disconnected is true even if just * is disconnected. Note that link->disconnected is true even if just
* one of the two links (commands and pub/sub) is missing. */ * one of the two links (commands and pub/sub) is missing. */
@ -2010,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Commands connection. */ /* Commands connection. */
if (link->cc == NULL) { if (link->cc == NULL) {
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (link->cc->err) { if (!link->cc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
instanceLinkCloseConnection(link,link->cc);
} else if (link->cc->err) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s", sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
link->cc->errstr); link->cc->errstr);
instanceLinkCloseConnection(link,link->cc); instanceLinkCloseConnection(link,link->cc);
@ -2033,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Pub / Sub */ /* Pub / Sub */
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) { if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (link->pc->err) { if (!link->pc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
} else if (link->pc->err) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s", sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
link->pc->errstr); link->pc->errstr);
instanceLinkCloseConnection(link,link->pc); instanceLinkCloseConnection(link,link->pc);

View File

@ -2229,11 +2229,13 @@ void initServerConfig(void) {
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ; server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
server.arch_bits = (sizeof(long) == 8) ? 64 : 32; server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
server.port = CONFIG_DEFAULT_SERVER_PORT; server.port = CONFIG_DEFAULT_SERVER_PORT;
server.tls_port = CONFIG_DEFAULT_SERVER_TLS_PORT;
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG; server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
server.bindaddr_count = 0; server.bindaddr_count = 0;
server.unixsocket = NULL; server.unixsocket = NULL;
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM; server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
server.ipfd_count = 0; server.ipfd_count = 0;
server.tlsfd_count = 0;
server.sofd = -1; server.sofd = -1;
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE; server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED; server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
@ -2349,7 +2351,7 @@ void initServerConfig(void) {
server.repl_state = REPL_STATE_NONE; server.repl_state = REPL_STATE_NONE;
server.repl_transfer_tmpfile = NULL; server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1; server.repl_transfer_fd = -1;
server.repl_transfer_s = -1; server.repl_transfer_s = NULL;
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT; server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA; server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY; server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
@ -2430,6 +2432,9 @@ void initServerConfig(void) {
* script to the slave / AOF. This is the new way starting from * script to the slave / AOF. This is the new way starting from
* Redis 5. However it is possible to revert it via redis.conf. */ * Redis 5. However it is possible to revert it via redis.conf. */
server.lua_always_replicate_commands = 1; server.lua_always_replicate_commands = 1;
/* TLS */
server.tls_auth_clients = 1;
} }
extern char **environ; extern char **environ;
@ -2746,6 +2751,11 @@ void initServer(void) {
server.clients_paused = 0; server.clients_paused = 0;
server.system_memory_size = zmalloc_get_memory_size(); server.system_memory_size = zmalloc_get_memory_size();
if (server.tls_port && tlsConfigureServer() == C_ERR) {
serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info.");
exit(1);
}
createSharedObjects(); createSharedObjects();
adjustOpenFilesLimit(); adjustOpenFilesLimit();
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR); server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
@ -2761,6 +2771,9 @@ void initServer(void) {
if (server.port != 0 && if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR) listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
exit(1); exit(1);
if (server.tls_port != 0 &&
listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
exit(1);
/* Open the listening Unix domain socket. */ /* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) { if (server.unixsocket != NULL) {
@ -2775,7 +2788,7 @@ void initServer(void) {
} }
/* Abort if there are no listening sockets at all. */ /* Abort if there are no listening sockets at all. */
if (server.ipfd_count == 0 && server.sofd < 0) { if (server.ipfd_count == 0 && server.tlsfd_count == 0 && server.sofd < 0) {
serverLog(LL_WARNING, "Configured to not listen anywhere, exiting."); serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
exit(1); exit(1);
} }
@ -2845,6 +2858,14 @@ void initServer(void) {
"Unrecoverable error creating server.ipfd file event."); "Unrecoverable error creating server.ipfd file event.");
} }
} }
for (j = 0; j < server.tlsfd_count; j++) {
if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
acceptTLSHandler,NULL) == AE_ERR)
{
serverPanic(
"Unrecoverable error creating server.tlsfd file event.");
}
}
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event."); acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
@ -3536,6 +3557,7 @@ void closeListeningSockets(int unlink_unix_socket) {
int j; int j;
for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]); for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]);
for (j = 0; j < server.tlsfd_count; j++) close(server.tlsfd[j]);
if (server.sofd != -1) close(server.sofd); if (server.sofd != -1) close(server.sofd);
if (server.cluster_enabled) if (server.cluster_enabled)
for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]); for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]);
@ -4274,7 +4296,7 @@ sds genRedisInfoString(char *section) {
long lag = 0; long lag = 0;
if (slaveip[0] == '\0') { if (slaveip[0] == '\0') {
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1) if (connPeerToString(slave->conn,ip,sizeof(ip),&port) == -1)
continue; continue;
slaveip = ip; slaveip = ip;
} }
@ -4516,7 +4538,7 @@ void redisAsciiArt(void) {
redisGitSHA1(), redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0, strtol(redisGitDirty(),NULL,10) > 0,
(sizeof(long) == 8) ? "64" : "32", (sizeof(long) == 8) ? "64" : "32",
mode, server.port, mode, server.port ? server.port : server.tls_port,
(long) getpid() (long) getpid()
); );
serverLogRaw(LL_NOTICE|LL_RAW,buf); serverLogRaw(LL_NOTICE|LL_RAW,buf);
@ -4642,7 +4664,7 @@ void redisSetProcTitle(char *title) {
setproctitle("%s %s:%d%s", setproctitle("%s %s:%d%s",
title, title,
server.bindaddr_count ? server.bindaddr[0] : "*", server.bindaddr_count ? server.bindaddr[0] : "*",
server.port, server.port ? server.port : server.tls_port,
server_mode); server_mode);
#else #else
UNUSED(title); UNUSED(title);
@ -4793,6 +4815,7 @@ int main(int argc, char **argv) {
ACLInit(); /* The ACL subsystem must be initialized ASAP because the ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */ basic networking code and client creation depends on it. */
moduleInitModulesSystem(); moduleInitModulesSystem();
tlsInit();
/* Store the executable path and arguments in a safe place in order /* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */ * to be able to restart the server later. */
@ -4925,7 +4948,7 @@ int main(int argc, char **argv) {
exit(1); exit(1);
} }
} }
if (server.ipfd_count > 0) if (server.ipfd_count > 0 || server.tlsfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections"); serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0) if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket); serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);

View File

@ -66,6 +66,7 @@ typedef long long mstime_t; /* millisecond time type. */
#include "quicklist.h" /* Lists are encoded as linked lists of #include "quicklist.h" /* Lists are encoded as linked lists of
N-elements flat arrays */ N-elements flat arrays */
#include "rax.h" /* Radix tree */ #include "rax.h" /* Radix tree */
#include "connection.h" /* Connection abstraction */
/* Following includes allow test functions to be called from Redis main() */ /* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h" #include "zipmap.h"
@ -84,6 +85,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_MAX_HZ 500 #define CONFIG_MAX_HZ 500
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */ #define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */ #define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
#define CONFIG_DEFAULT_SERVER_TLS_PORT 0 /* TCP port. */
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */ #define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */ #define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
#define CONFIG_DEFAULT_DBNUM 16 #define CONFIG_DEFAULT_DBNUM 16
@ -818,7 +820,7 @@ typedef struct user {
* Clients are taken in a linked list. */ * Clients are taken in a linked list. */
typedef struct client { typedef struct client {
uint64_t id; /* Client incremental unique ID. */ uint64_t id; /* Client incremental unique ID. */
int fd; /* Client socket. */ connection *conn;
int resp; /* RESP protocol version. Can be 2 or 3. */ int resp; /* RESP protocol version. Can be 2 or 3. */
redisDb *db; /* Pointer to currently SELECTed DB. */ redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */ robj *name; /* As set by CLIENT SETNAME. */
@ -1078,6 +1080,7 @@ struct redisServer {
to be processed. */ to be processed. */
/* Networking */ /* Networking */
int port; /* TCP listening port */ int port; /* TCP listening port */
int tls_port; /* TLS listening port */
int tcp_backlog; /* TCP listen() backlog */ int tcp_backlog; /* TCP listen() backlog */
char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */ char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */
int bindaddr_count; /* Number of addresses in server.bindaddr[] */ int bindaddr_count; /* Number of addresses in server.bindaddr[] */
@ -1085,6 +1088,8 @@ struct redisServer {
mode_t unixsocketperm; /* UNIX socket permission */ mode_t unixsocketperm; /* UNIX socket permission */
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */ int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */ int ipfd_count; /* Used slots in ipfd[] */
int tlsfd[CONFIG_BINDADDR_MAX]; /* TLS socket file descriptors */
int tlsfd_count; /* Used slots in tlsfd[] */
int sofd; /* Unix socket file descriptor */ int sofd; /* Unix socket file descriptor */
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */ int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
int cfd_count; /* Used slots in cfd[] */ int cfd_count; /* Used slots in cfd[] */
@ -1287,7 +1292,7 @@ struct redisServer {
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */ off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */ off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */ off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
int repl_transfer_s; /* Slave -> Master SYNC socket */ connection *repl_transfer_s; /* Slave -> Master SYNC connection */
int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */ int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */
char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */ char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */ time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
@ -1410,6 +1415,14 @@ struct redisServer {
int watchdog_period; /* Software watchdog period in ms. 0 = off */ int watchdog_period; /* Software watchdog period in ms. 0 = off */
/* System hardware info */ /* System hardware info */
size_t system_memory_size; /* Total memory in system as reported by OS */ size_t system_memory_size; /* Total memory in system as reported by OS */
/* TLS Configuration */
int tls_cluster;
int tls_replication;
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 { typedef struct pubsubPattern {
@ -1553,12 +1566,12 @@ size_t redisPopcount(void *s, long count);
void redisSetProcTitle(char *title); void redisSetProcTitle(char *title);
/* networking.c -- Networking and Client related operations */ /* networking.c -- Networking and Client related operations */
client *createClient(int fd); client *createClient(connection *conn);
void closeTimedoutClients(void); void closeTimedoutClients(void);
void freeClient(client *c); void freeClient(client *c);
void freeClientAsync(client *c); void freeClientAsync(client *c);
void resetClient(client *c); void resetClient(client *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask); void sendReplyToClient(connection *conn);
void *addReplyDeferredLen(client *c); void *addReplyDeferredLen(client *c);
void setDeferredArrayLen(client *c, void *node, long length); void setDeferredArrayLen(client *c, void *node, long length);
void setDeferredMapLen(client *c, void *node, long length); void setDeferredMapLen(client *c, void *node, long length);
@ -1570,8 +1583,9 @@ void processInputBufferAndReplicate(client *c);
void processGopherRequest(client *c); void processGopherRequest(client *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); void readQueryFromClient(connection *conn);
void addReplyNull(client *c); void addReplyNull(client *c);
void addReplyNullArray(client *c); void addReplyNullArray(client *c);
void addReplyBool(client *c, int b); void addReplyBool(client *c, int b);
@ -1629,7 +1643,7 @@ int handleClientsWithPendingReadsUsingThreads(void);
int stopThreadedIOIfNeeded(void); int stopThreadedIOIfNeeded(void);
int clientHasPendingReplies(client *c); int clientHasPendingReplies(client *c);
void unlinkClient(client *c); void unlinkClient(client *c);
int writeToClient(int fd, client *c, int handler_installed); int writeToClient(client *c, int handler_installed);
void linkClient(client *c); void linkClient(client *c);
void protectClient(client *c); void protectClient(client *c);
void unprotectClient(client *c); void unprotectClient(client *c);
@ -2343,6 +2357,12 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len);
void xorDigest(unsigned char *digest, void *ptr, size_t len); void xorDigest(unsigned char *digest, void *ptr, size_t len);
int populateCommandTableParseFlags(struct redisCommand *c, char *strflags); int populateCommandTableParseFlags(struct redisCommand *c, char *strflags);
/* TLS stuff */
void tlsInit(void);
int 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, ...) \ #define redisDebug(fmt, ...) \
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__) printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define redisDebugMark() \ #define redisDebugMark() \

669
src/tls.c Normal file
View File

@ -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

View File

@ -8,6 +8,7 @@ source ../instances.tcl
source ../../support/cluster.tcl ; # Redis Cluster client. source ../../support/cluster.tcl ; # Redis Cluster client.
set ::instances_count 20 ; # How many instances we use at max. set ::instances_count 20 ; # How many instances we use at max.
set ::tlsdir "../../tls"
proc main {} { proc main {} {
parse_options parse_options

View File

@ -4,6 +4,7 @@
# are preseved across iterations. # are preseved across iterations.
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
test "Create a 5 nodes cluster" { test "Create a 5 nodes cluster" {
create_cluster 5 5 create_cluster 5 5
@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
--cluster-to $target \ --cluster-to $target \
--cluster-slots 100 \ --cluster-slots 100 \
--cluster-yes \ --cluster-yes \
{*}[rediscli_tls_config "../../../tests"] \
| [info nameofexecutable] \ | [info nameofexecutable] \
../tests/helpers/onlydots.tcl \ ../tests/helpers/onlydots.tcl \
&] 0] &] 0]

View File

@ -5,6 +5,7 @@
# other masters have slaves. # other masters have slaves.
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no # Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test. # empty masters and make rebalancing simpler to handle during the test.
@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" {
set output [exec \ set output [exec \
../../../src/redis-cli --cluster rebalance \ ../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \ 127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=0 >@ stdout ] --cluster-weight ${master0_id}=0 >@ stdout ]
} }
test "Master #0 should lose its replicas" { test "Master #0 should lose its replicas" {
@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
set output [exec \ set output [exec \
../../../src/redis-cli --cluster rebalance \ ../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \ 127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=.01 \ --cluster-weight ${master0_id}=.01 \
--cluster-use-empty-masters >@ stdout] --cluster-use-empty-masters >@ stdout]
} }

View File

@ -1,6 +1,8 @@
source tests/support/redis.tcl source tests/support/redis.tcl
source tests/support/util.tcl source tests/support/util.tcl
set ::tlsdir "tests/tls"
# This function sometimes writes sometimes blocking-reads from lists/sorted # This function sometimes writes sometimes blocking-reads from lists/sorted
# sets. There are multiple processes like this executing at the same time # sets. There are multiple processes like this executing at the same time
# so that we have some chance to trap some corner condition if there is # so that we have some chance to trap some corner condition if there is
@ -8,8 +10,8 @@ source tests/support/util.tcl
# space to just a few elements, and balance the operations so that it is # space to just a few elements, and balance the operations so that it is
# unlikely that lists and zsets just get more data without ever causing # unlikely that lists and zsets just get more data without ever causing
# blocking. # blocking.
proc bg_block_op {host port db ops} { proc bg_block_op {host port db ops tls} {
set r [redis $host $port] set r [redis $host $port 0 $tls]
$r select $db $r select $db
for {set j 0} {$j < $ops} {incr j} { for {set j 0} {$j < $ops} {incr j} {
@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} {
} }
} }
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]

View File

@ -1,10 +1,12 @@
source tests/support/redis.tcl source tests/support/redis.tcl
source tests/support/util.tcl source tests/support/util.tcl
proc bg_complex_data {host port db ops} { set ::tlsdir "tests/tls"
set r [redis $host $port]
proc bg_complex_data {host port db ops tls} {
set r [redis $host $port 0 $tls]
$r select $db $r select $db
createComplexDataset $r $ops createComplexDataset $r $ops
} }
bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]

View File

@ -1,8 +1,10 @@
source tests/support/redis.tcl source tests/support/redis.tcl
proc gen_write_load {host port seconds} { set ::tlsdir "tests/tls"
proc gen_write_load {host port seconds tls} {
set start_time [clock seconds] set start_time [clock seconds]
set r [redis $host $port 1] set r [redis $host $port 0 $tls]
$r select 9 $r select 9
while 1 { while 1 {
$r set [expr rand()] [expr rand()] $r set [expr rand()] [expr rand()]
@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
} }
} }
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]

View File

@ -17,6 +17,7 @@ source ../support/test.tcl
set ::verbose 0 set ::verbose 0
set ::valgrind 0 set ::valgrind 0
set ::tls 0
set ::pause_on_error 0 set ::pause_on_error 0
set ::simulate_error 0 set ::simulate_error 0
set ::failed 0 set ::failed 0
@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} {
# Write the instance config file. # Write the instance config file.
set cfgfile [file join $dirname $type.conf] set cfgfile [file join $dirname $type.conf]
set cfg [open $cfgfile w] set cfg [open $cfgfile w]
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 "dir ./$dirname"
puts $cfg "logfile log.txt" puts $cfg "logfile log.txt"
# Add additional config files # Add additional config files
@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} {
} }
# Push the instance into the right list # Push the instance into the right list
set link [redis 127.0.0.1 $port] set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1 $link reconnect 1
lappend ::${type}_instances [list \ lappend ::${type}_instances [list \
pid $pid \ pid $pid \
@ -148,6 +161,13 @@ proc parse_options {} {
set ::simulate_error 1 set ::simulate_error 1
} elseif {$opt eq {--valgrind}} { } elseif {$opt eq {--valgrind}} {
set ::valgrind 1 set ::valgrind 1
} elseif {$opt eq {--tls}} {
package require tls 1.6
::tls::init \
-cafile "$::tlsdir/ca.crt" \
-certfile "$::tlsdir/redis.crt" \
-keyfile "$::tlsdir/redis.key"
set ::tls 1
} elseif {$opt eq "--help"} { } elseif {$opt eq "--help"} {
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests." puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts "\nOptions:" puts "\nOptions:"
@ -492,7 +512,7 @@ proc restart_instance {type id} {
} }
# Connect with it with a fresh link # Connect with it with a fresh link
set link [redis 127.0.0.1 $port] set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1 $link reconnect 1
set_instance_attrib $type $id link $link set_instance_attrib $type $id link $link

View File

@ -13,8 +13,9 @@ tags {"aof"} {
# cleaned after a child responsible for an AOF rewrite exited. This buffer # cleaned after a child responsible for an AOF rewrite exited. This buffer
# was subsequently appended to the new AOF, resulting in duplicate commands. # was subsequently appended to the new AOF, resulting in duplicate commands.
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
set client [redis [srv host] [srv port]] set client [redis [srv host] [srv port] 0 $::tls]
set bench [open "|src/redis-benchmark -q -p [srv port] -c 20 -n 20000 incr foo" "r+"] set bench [open "|src/redis-benchmark -q -s [srv unixsocket] -c 20 -n 20000 incr foo" "r+"]
after 100 after 100
# Benchmark should be running by now: start background rewrite # Benchmark should be running by now: start background rewrite
@ -29,7 +30,7 @@ tags {"aof"} {
# Restart server to replay AOF # Restart server to replay AOF
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
set client [redis [srv host] [srv port]] set client [redis [srv host] [srv port] 0 $::tls]
assert_equal 20000 [$client get foo] assert_equal 20000 [$client get foo]
} }
} }

View File

@ -52,7 +52,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv] assert_equal 1 [is_alive $srv]
} }
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 5" { test "Truncated AOF loaded: we expect foo to be equal to 5" {
assert {[$client get foo] eq "5"} assert {[$client get foo] eq "5"}
@ -69,7 +69,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv] assert_equal 1 [is_alive $srv]
} }
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 6 now" { test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
assert {[$client get foo] eq "6"} assert {[$client get foo] eq "6"}
@ -170,7 +170,7 @@ tags {"aof"} {
} }
test "Fixed AOF: Keyspace should contain values that were parseable" { test "Fixed AOF: Keyspace should contain values that were parseable" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {
@ -194,7 +194,7 @@ tags {"aof"} {
} }
test "AOF+SPOP: Set should have 1 member" { test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {
@ -218,7 +218,7 @@ tags {"aof"} {
} }
test "AOF+SPOP: Set should have 1 member" { test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {
@ -241,7 +241,7 @@ tags {"aof"} {
} }
test "AOF+EXPIRE: List should be empty" { test "AOF+EXPIRE: List should be empty" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {

View File

@ -2,9 +2,9 @@
# Unlike stream operations such operations are "pop" style, so they consume # Unlike stream operations such operations are "pop" style, so they consume
# the list or sorted set, and must be replicated correctly. # the list or sorted set, and must be replicated correctly.
proc start_bg_block_op {host port db ops} { proc start_bg_block_op {host port db ops tls} {
set tclsh [info nameofexecutable] set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_block_op.tcl $host $port $db $ops & exec $tclsh tests/helpers/bg_block_op.tcl $host $port $db $ops $tls &
} }
proc stop_bg_block_op {handle} { proc stop_bg_block_op {handle} {
@ -18,9 +18,9 @@ start_server {tags {"repl"}} {
set master_port [srv -1 port] set master_port [srv -1 port]
set slave [srv 0 client] set slave [srv 0 client]
set load_handle0 [start_bg_block_op $master_host $master_port 9 100000] set load_handle0 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
set load_handle1 [start_bg_block_op $master_host $master_port 9 100000] set load_handle1 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
set load_handle2 [start_bg_block_op $master_host $master_port 9 100000] set load_handle2 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
test {First server should have role slave after SLAVEOF} { test {First server should have role slave after SLAVEOF} {
$slave slaveof $master_host $master_port $slave slaveof $master_host $master_port

View File

@ -18,6 +18,7 @@ start_server {} {
set R($j) [srv [expr 0-$j] client] set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host] set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port] set R_port($j) [srv [expr 0-$j] port]
set R_unixsocket($j) [srv [expr 0-$j] unixsocket]
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"} if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
} }
@ -36,7 +37,7 @@ start_server {} {
} }
set cycle_start_time [clock milliseconds] set cycle_start_time [clock milliseconds]
set bench_pid [exec src/redis-benchmark -p $R_port(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &] set bench_pid [exec src/redis-benchmark -s $R_unixsocket(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
while 1 { while 1 {
set elapsed [expr {[clock milliseconds]-$cycle_start_time}] set elapsed [expr {[clock milliseconds]-$cycle_start_time}]
if {$elapsed > $duration*1000} break if {$elapsed > $duration*1000} break

View File

@ -1,7 +1,10 @@
source tests/support/cli.tcl
start_server {tags {"cli"}} { start_server {tags {"cli"}} {
proc open_cli {} { proc open_cli {} {
set ::env(TERM) dumb 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 -buffering none
fconfigure $fd -blocking false fconfigure $fd -blocking false
fconfigure $fd -translation binary fconfigure $fd -translation binary
@ -54,8 +57,8 @@ start_server {tags {"cli"}} {
} }
proc _run_cli {opts args} { proc _run_cli {opts args} {
set cmd [format "src/redis-cli -p %d -n 9 $args" [srv port]] set cmd [rediscli [srv port] [list -n 9 {*}$args]]
foreach {key value} $opts { foreach {key value} $args {
if {$key eq "pipe"} { if {$key eq "pipe"} {
set cmd "sh -c \"$value | $cmd\"" set cmd "sh -c \"$value | $cmd\""
} }

View File

@ -29,6 +29,9 @@ start_server {tags {"repl"}} {
$slave slaveof $master_host $master_port $slave slaveof $master_host $master_port
test {Slave enters handshake} { test {Slave enters handshake} {
if {$::tls} {
fail "TLS with repl-diskless-sync not supported yet."
}
wait_for_condition 50 1000 { wait_for_condition 50 1000 {
[string match *handshake* [$slave role]] [string match *handshake* [$slave role]]
} else { } else {
@ -184,6 +187,10 @@ start_server {tags {"repl"}} {
} }
foreach mdl {no yes} { 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} { foreach sdl {disabled swapdb} {
start_server {tags {"repl"}} { start_server {tags {"repl"}} {
set master [srv 0 client] set master [srv 0 client]
@ -320,6 +327,9 @@ start_server {tags {"repl"}} {
} }
test {slave fails full sync and diskless load swapdb recoveres it} { test {slave fails full sync and diskless load swapdb recoveres it} {
if {$::tls} {
fail ""
}
start_server {tags {"repl"}} { start_server {tags {"repl"}} {
set slave [srv 0 client] set slave [srv 0 client]
set slave_host [srv 0 host] 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} { test {diskless loading short read} {
if {$::tls} {
fail "TLS with repl-diskless-sync not supported yet."
}
start_server {tags {"repl"}} { start_server {tags {"repl"}} {
set replica [srv 0 client] set replica [srv 0 client]
set replica_host [srv 0 host] set replica_host [srv 0 host]

View File

@ -1,6 +1,7 @@
# Test conditions where an instance is considered to be down # Test conditions where an instance is considered to be down
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
proc ensure_master_up {} { proc ensure_master_up {} {
wait_for_condition 1000 50 { 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" { test "SDOWN is triggered by non-responding but not crashed instance" {
lassign [S 4 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] host port lassign [S 4 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] host port
ensure_master_up 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_down
ensure_master_up ensure_master_up
} }

19
tests/support/cli.tcl Normal file
View File

@ -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
}

View File

@ -62,7 +62,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
lassign [split $ip_port :] start_host start_port lassign [split $ip_port :] start_host start_port
if {[catch { if {[catch {
set r {} set r {}
set r [redis $start_host $start_port] set r [redis $start_host $start_port 0 $::tls]
set nodes_descr [$r cluster nodes] set nodes_descr [$r cluster nodes]
$r close $r close
} e]} { } e]} {
@ -107,7 +107,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
# Connect to the node # Connect to the node
set link {} set link {}
catch {set link [redis $host $port]} catch {set link [redis $host $port 0 $::tls]}
# Build this node description as an hash. # Build this node description as an hash.
set node [dict create \ set node [dict create \

View File

@ -39,8 +39,17 @@ array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks array set ::redis::statestack {} ;# Stack of states, for nested mbulks
proc redis {{server 127.0.0.1} {port 6379} {defer 0}} { proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0}} {
set fd [socket $server $port] 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 fconfigure $fd -translation binary
set id [incr ::redis::id] set id [incr ::redis::id]
set ::redis::fd($id) $fd 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::blocking($id) 1
set ::redis::deferred($id) $defer set ::redis::deferred($id) $defer
set ::redis::reconnect($id) 0 set ::redis::reconnect($id) 0
set ::redis::tls $tls
::redis::redis_reset_state $id ::redis::redis_reset_state $id
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $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. # Reconnect the link if needed.
if {$fd eq {}} { if {$fd eq {}} {
lassign $::redis::addr($id) host port 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 fconfigure $::redis::fd($id) -translation binary
set fd $::redis::fd($id) set fd $::redis::fd($id)
} }

View File

@ -92,7 +92,11 @@ proc is_alive config {
proc ping_server {host port} { proc ping_server {host port} {
set retval 0 set retval 0
if {[catch { 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 fconfigure $fd -translation binary
puts $fd "PING\r\n" puts $fd "PING\r\n"
flush $fd flush $fd
@ -136,7 +140,6 @@ proc tags {tags code} {
uplevel 1 $code uplevel 1 $code
set ::tags [lrange $::tags 0 end-[llength $tags]] set ::tags [lrange $::tags 0 end-[llength $tags]]
} }
proc start_server {options {code undefined}} { proc start_server {options {code undefined}} {
# If we are running against an external server, we just push the # If we are running against an external server, we just push the
# host/port pair in the stack the first time # host/port pair in the stack the first time
@ -145,7 +148,7 @@ proc start_server {options {code undefined}} {
set srv {} set srv {}
dict set srv "host" $::host dict set srv "host" $::host
dict set srv "port" $::port dict set srv "port" $::port
set client [redis $::host $::port] set client [redis $::host $::port 0 $::tls]
dict set srv "client" $client dict set srv "client" $client
$client select 9 $client select 9
@ -178,6 +181,13 @@ proc start_server {options {code undefined}} {
set data [split [exec cat "tests/assets/$baseconfig"] "\n"] set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
set config {} 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 { foreach line $data {
if {[string length $line] > 0 && [string index $line 0] ne "#"} { if {[string length $line] > 0 && [string index $line 0] ne "#"} {
set elements [split $line " "] set elements [split $line " "]
@ -192,7 +202,17 @@ proc start_server {options {code undefined}} {
# start every server on a different port # start every server on a different port
set ::port [find_available_port [expr {$::port+1}]] 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 # apply overrides from global space and arguments
foreach {directive arguments} [concat $::global_overrides $overrides] { 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 # setup properties to be able to initialize a client object
set port_param [expr $::tls ? {"tls-port"} : {"port"}]
set host $::host set host $::host
set port $::port set port $::port
if {[dict exists $config bind]} { set host [dict get $config bind] } 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 # setup config dict
dict set srv "config_file" $config_file 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 "port" $port
dict set srv "stdout" $stdout dict set srv "stdout" $stdout
dict set srv "stderr" $stderr dict set srv "stderr" $stderr
dict set srv "unixsocket" $unixsocket
# if a block of code is supplied, we wait for the server to become # if a block of code is supplied, we wait for the server to become
# available, create a client object and kill the server afterwards # available, create a client object and kill the server afterwards

View File

@ -395,7 +395,7 @@ proc colorstr {color str} {
# of seconds to the specified Redis instance. # of seconds to the specified Redis instance.
proc start_write_load {host port seconds} { proc start_write_load {host port seconds} {
set tclsh [info nameofexecutable] 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. # 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. # of ops to the specified Redis instance.
proc start_bg_complex_data {host port db ops} { proc start_bg_complex_data {host port db ops} {
set tclsh [info nameofexecutable] 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. # Stop a process generating write load executed with start_bg_complex_data.

View File

@ -63,6 +63,7 @@ set ::all_tests {
unit/lazyfree unit/lazyfree
unit/wait unit/wait
unit/pendingquerybuf unit/pendingquerybuf
unit/tls
} }
# Index to the next test to run in the ::all_tests list. # Index to the next test to run in the ::all_tests list.
set ::next_test 0 set ::next_test 0
@ -71,6 +72,7 @@ set ::host 127.0.0.1
set ::port 21111 set ::port 21111
set ::traceleaks 0 set ::traceleaks 0
set ::valgrind 0 set ::valgrind 0
set ::tls 0
set ::stack_logging 0 set ::stack_logging 0
set ::verbose 0 set ::verbose 0
set ::quiet 0 set ::quiet 0
@ -92,6 +94,7 @@ set ::dont_clean 0
set ::wait_server 0 set ::wait_server 0
set ::stop_on_failure 0 set ::stop_on_failure 0
set ::loop 0 set ::loop 0
set ::tlsdir "tests/tls"
# Set to 1 when we are running in client mode. The Redis test uses a # 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 # server-client model to run tests simultaneously. The server instance
@ -146,7 +149,7 @@ proc reconnect {args} {
set host [dict get $srv "host"] set host [dict get $srv "host"]
set port [dict get $srv "port"] set port [dict get $srv "port"]
set config [dict get $srv "config"] set config [dict get $srv "config"]
set client [redis $host $port] set client [redis $host $port 0 $::tls]
dict set srv "client" $client dict set srv "client" $client
# select the right db when we don't have to authenticate # 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 # 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) # select the right db and read the response (OK)
$client select 9 $client select 9
@ -204,7 +207,7 @@ proc test_server_main {} {
if {!$::quiet} { if {!$::quiet} {
puts "Starting test server at port $port" 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 # Start the client instances
set ::clients_pids {} set ::clients_pids {}
@ -450,6 +453,7 @@ proc print_help_screen {} {
"--stop Blocks once the first test fails." "--stop Blocks once the first test fails."
"--loop Execute the specified set of tests forever." "--loop Execute the specified set of tests forever."
"--wait-server Wait after server is started (so that you can attach a debugger)." "--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." "--help Print this help screen."
} "\n"] } "\n"]
} }
@ -486,6 +490,13 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} }
} elseif {$opt eq {--quiet}} { } elseif {$opt eq {--quiet}} {
set ::quiet 1 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}} { } elseif {$opt eq {--host}} {
set ::external 1 set ::external 1
set ::host $arg set ::host $arg
@ -565,7 +576,11 @@ if {[llength $::single_tests] > 0} {
} }
proc attach_to_replication_stream {} { 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 fconfigure $s -translation binary
puts -nonewline $s "SYNC\r\n" puts -nonewline $s "SYNC\r\n"
flush $s flush $s

View File

@ -1,4 +1,9 @@
start_server {tags {"limits"} overrides {maxclients 10}} { 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} { test {Check if maxclients works refusing connections} {
set c 0 set c 0
catch { catch {
@ -12,5 +17,5 @@ start_server {tags {"limits"} overrides {maxclients 10}} {
} e } e
assert {$c > 8 && $c <= 10} assert {$c > 8 && $c <= 10}
set e set e
} {*ERR max*reached*} } $expected_code
} }

View File

@ -166,7 +166,11 @@ start_server {tags {"other"}} {
tags {protocol} { tags {protocol} {
test {PIPELINING stresser (also a regression for the old epoll bug)} { 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 fconfigure $fd2 -encoding binary -translation binary
puts -nonewline $fd2 "SELECT 9\r\n" puts -nonewline $fd2 "SELECT 9\r\n"
flush $fd2 flush $fd2

View File

@ -72,7 +72,11 @@ start_server {tags {"protocol"}} {
foreach seq [list "\x00" "*\x00" "$\x00"] { foreach seq [list "\x00" "*\x00" "$\x00"] {
incr c incr c
test "Protocol desync regression test #$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 puts -nonewline $s $seq
set payload [string repeat A 1024]"\n" set payload [string repeat A 1024]"\n"
set test_start [clock seconds] set test_start [clock seconds]

25
tests/unit/tls.tcl Normal file
View File

@ -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
} {}
}
}

View File

@ -1,3 +1,5 @@
source tests/support/cli.tcl
start_server {tags {"wait"}} { start_server {tags {"wait"}} {
start_server {} { start_server {} {
set slave [srv 0 client] set slave [srv 0 client]
@ -31,7 +33,8 @@ start_server {} {
} }
test {WAIT should not acknowledge 1 additional copy if slave is blocked} { 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. after 1000 ;# Give redis-cli the time to execute the command.
$master set foo 0 $master set foo 0
$master incr foo $master incr foo

23
utils/gen-test-certs.sh Executable file
View File

@ -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