SSL for async I/O
This commit is contained in:
parent
ba947bc93c
commit
08efa46599
6
Makefile
6
Makefile
@ -4,7 +4,8 @@
|
|||||||
# 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 sslio.o
|
OBJ=net.o hiredis.o sds.o async.o read.o sslio.o
|
||||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-ssl
|
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib \
|
||||||
|
hiredis-example-ssl hiredis-example-libevent-ssl
|
||||||
TESTS=hiredis-test
|
TESTS=hiredis-test
|
||||||
LIBNAME=libhiredis
|
LIBNAME=libhiredis
|
||||||
PKGCONFNAME=hiredis.pc
|
PKGCONFNAME=hiredis.pc
|
||||||
@ -94,6 +95,9 @@ static: $(STLIBNAME)
|
|||||||
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) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
|
||||||
|
|
||||||
|
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
|
||||||
|
|
||||||
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) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
|
||||||
|
|
||||||
|
92
async.c
92
async.c
@ -40,6 +40,7 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "dict.c"
|
#include "dict.c"
|
||||||
#include "sds.h"
|
#include "sds.h"
|
||||||
|
#include "sslio.h"
|
||||||
|
|
||||||
#define _EL_ADD_READ(ctx) do { \
|
#define _EL_ADD_READ(ctx) do { \
|
||||||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||||
@ -524,6 +525,87 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef HIREDIS_NOSSL
|
||||||
|
/**
|
||||||
|
* Handle SSL when socket becomes available for reading. This also handles
|
||||||
|
* read-while-write and write-while-read
|
||||||
|
*/
|
||||||
|
static void asyncSslRead(redisAsyncContext *ac) {
|
||||||
|
int rv;
|
||||||
|
redisSsl *ssl = ac->c.ssl;
|
||||||
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
|
ssl->wantRead = 0;
|
||||||
|
|
||||||
|
if (ssl->pendingWrite) {
|
||||||
|
int done;
|
||||||
|
|
||||||
|
/* This is probably just a write event */
|
||||||
|
ssl->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SSL when socket becomes available for writing
|
||||||
|
*/
|
||||||
|
static void asyncSslWrite(redisAsyncContext *ac) {
|
||||||
|
int rv, done = 0;
|
||||||
|
redisSsl *ssl = ac->c.ssl;
|
||||||
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
|
ssl->pendingWrite = 0;
|
||||||
|
rv = redisBufferWrite(c, &done);
|
||||||
|
if (rv == REDIS_ERR) {
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
if (ssl->wantRead) {
|
||||||
|
/* Need to read-before-write */
|
||||||
|
ssl->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);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Just so we're able to compile */
|
||||||
|
static void asyncSslRead(redisAsyncContext *ac) {
|
||||||
|
abort();
|
||||||
|
(void)ac;
|
||||||
|
}
|
||||||
|
static void asyncSslWrite(redisAsyncContext *ac) {
|
||||||
|
abort();
|
||||||
|
(void)ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/* 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,6 +621,11 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c->flags & REDIS_SSL) {
|
||||||
|
asyncSslRead(ac);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (redisBufferRead(c) == REDIS_ERR) {
|
if (redisBufferRead(c) == REDIS_ERR) {
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
} else {
|
} else {
|
||||||
@ -561,6 +648,11 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c->flags & REDIS_SSL) {
|
||||||
|
asyncSslWrite(ac);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
} else {
|
} else {
|
||||||
|
72
examples/example-libevent-ssl.c
Normal file
72
examples/example-libevent-ssl.c
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include <hiredis.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) != 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;
|
||||||
|
}
|
22
sslio.c
22
sslio.c
@ -99,7 +99,7 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath,
|
|||||||
|
|
||||||
redisSsl *s = c->ssl;
|
redisSsl *s = c->ssl;
|
||||||
s->ctx = SSL_CTX_new(SSLv23_client_method());
|
s->ctx = SSL_CTX_new(SSLv23_client_method());
|
||||||
/* SSL_CTX_set_info_callback(s->ctx, sslLogCallback); */
|
SSL_CTX_set_info_callback(s->ctx, sslLogCallback);
|
||||||
SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||||
SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||||
SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL);
|
SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL);
|
||||||
@ -153,6 +153,22 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath,
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int maybeCheckWant(redisSsl *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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
|
int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
int nread = SSL_read(c->ssl->ssl, buf, bufcap);
|
int nread = SSL_read(c->ssl->ssl, buf, bufcap);
|
||||||
if (nread > 0) {
|
if (nread > 0) {
|
||||||
@ -162,7 +178,7 @@ int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
|
|||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
int err = SSL_get_error(c->ssl->ssl, nread);
|
int err = SSL_get_error(c->ssl->ssl, nread);
|
||||||
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
if (maybeCheckWant(c->ssl, err)) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
@ -181,7 +197,7 @@ int redisSslWrite(redisContext *c) {
|
|||||||
c->ssl->lastLen = len;
|
c->ssl->lastLen = len;
|
||||||
|
|
||||||
int err = SSL_get_error(c->ssl->ssl, rv);
|
int err = SSL_get_error(c->ssl->ssl, rv);
|
||||||
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
if (maybeCheckWant(c->ssl, err)) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
9
sslio.h
9
sslio.h
@ -33,6 +33,15 @@ typedef struct redisSsl {
|
|||||||
* previously called with in the event of an SSL_read/SSL_write situation
|
* previously called with in the event of an SSL_read/SSL_write situation
|
||||||
*/
|
*/
|
||||||
size_t lastLen;
|
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;
|
||||||
} redisSsl;
|
} redisSsl;
|
||||||
|
|
||||||
struct redisContext;
|
struct redisContext;
|
||||||
|
Loading…
Reference in New Issue
Block a user