Skip to content

Commit bacc1cc

Browse files
committed
Fix connect timeout in HiredisConnection#init_ssl
Ref: redis/hiredis#1059 Unfortunately I had to patch hiredis to expose wether the handshake was completed or not.
1 parent 4ec3e98 commit bacc1cc

File tree

4 files changed

+142
-51
lines changed

4 files changed

+142
-51
lines changed

ext/redis_client/hiredis/hiredis_connection.c

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,9 @@ static int hiredis_wait_writable(int fd, const struct timeval *timeout, int *iss
380380
struct timeval to;
381381
struct timeval *toptr = NULL;
382382

383-
rb_fdset_t fds;
384-
385383
/* Be cautious: a call to rb_fd_init to initialize the rb_fdset_t structure
386384
* must be paired with a call to rb_fd_term to free it. */
385+
rb_fdset_t fds;
387386
rb_fd_init(&fds);
388387
rb_fd_set(fd, &fds);
389388

@@ -406,6 +405,42 @@ static int hiredis_wait_writable(int fd, const struct timeval *timeout, int *iss
406405
return 0;
407406
}
408407

408+
static int hiredis_wait(hiredis_connection_t *connection, const struct timeval *timeout, bool *readable, bool *writable) {
409+
struct timeval to;
410+
struct timeval *toptr = NULL;
411+
412+
/* Be cautious: a call to rb_fd_init to initialize the rb_fdset_t structure
413+
* must be paired with a call to rb_fd_term to free it. */
414+
rb_fdset_t read_fds;
415+
if (readable) {
416+
*readable = false;
417+
rb_fd_init(&read_fds);
418+
rb_fd_set(connection->context->fd, &read_fds);
419+
}
420+
421+
rb_fdset_t write_fds;
422+
if (writable) {
423+
*writable = false;
424+
rb_fd_init(&write_fds);
425+
rb_fd_set(connection->context->fd, &write_fds);
426+
}
427+
428+
int error = rb_thread_fd_select(connection->context->fd + 1, readable ? &read_fds : NULL, writable ? &write_fds : NULL, NULL, toptr);
429+
if (error > 0) {
430+
error = 0;
431+
if (readable) *readable = (bool)rb_fd_isset(connection->context->fd, &read_fds);
432+
if (writable) *writable = (bool)rb_fd_isset(connection->context->fd, &write_fds);
433+
} else if (!errno) {
434+
errno = ETIMEDOUT;
435+
error = -2;
436+
}
437+
438+
if (readable) rb_fd_term(&read_fds);
439+
if (writable) rb_fd_term(&write_fds);
440+
441+
return error;
442+
}
443+
409444
static VALUE hiredis_connect_finish(hiredis_connection_t *connection, redisContext *context) {
410445
if (context->err) {
411446
redis_raise_error_and_disconnect(context, rb_eRedisClientConnectTimeoutError);
@@ -466,6 +501,24 @@ static VALUE hiredis_init_ssl(VALUE self, VALUE ssl_param) {
466501
if (redisInitiateSSLWithContext(connection->context, ssl_context->context) != REDIS_OK) {
467502
hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
468503
}
504+
505+
redisSSL *redis_ssl = redisGetSSLSocket(connection->context);
506+
507+
if (redis_ssl->wantRead) {
508+
int readable = 0;
509+
if (hiredis_wait_readable(connection->context->fd, &connection->connect_timeout, &readable) < 0) {
510+
hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
511+
}
512+
if (!readable) {
513+
errno = EAGAIN;
514+
hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
515+
}
516+
517+
if (redisInitiateSSLContinue(connection->context) != REDIS_OK) {
518+
hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
519+
};
520+
}
521+
469522
return Qtrue;
470523
}
471524

ext/redis_client/hiredis/vendor/hiredis_ssl.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
#ifndef __HIREDIS_SSL_H
3333
#define __HIREDIS_SSL_H
3434

35+
#include <openssl/ssl.h>
36+
3537
#ifdef __cplusplus
3638
extern "C" {
3739
#endif
@@ -46,6 +48,29 @@ struct ssl_st;
4648
*/
4749
typedef struct redisSSLContext redisSSLContext;
4850

51+
/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
52+
typedef struct redisSSL {
53+
/**
54+
* OpenSSL SSL object.
55+
*/
56+
SSL *ssl;
57+
58+
/**
59+
* SSL_write() requires to be called again with the same arguments it was
60+
* previously called with in the event of an SSL_read/SSL_write situation
61+
*/
62+
size_t lastLen;
63+
64+
/** Whether the SSL layer requires read (possibly before a write) */
65+
int wantRead;
66+
67+
/**
68+
* Whether a write was requested prior to a read. If set, the write()
69+
* should resume whenever a read takes place, if possible
70+
*/
71+
int pendingWrite;
72+
} redisSSL;
73+
4974
/**
5075
* Initialization errors that redisCreateSSLContext() may return.
5176
*/
@@ -114,12 +139,17 @@ void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);
114139

115140
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);
116141

142+
int redisInitiateSSLContinue(redisContext *c);
143+
117144
/**
118145
* Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.
119146
*/
120147

121148
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
122149

150+
151+
redisSSL *redisGetSSLSocket(redisContext *c);
152+
123153
#ifdef __cplusplus
124154
}
125155
#endif

ext/redis_client/hiredis/vendor/ssl.c

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -59,29 +59,6 @@ struct redisSSLContext {
5959
char *server_name;
6060
};
6161

62-
/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
63-
typedef struct redisSSL {
64-
/**
65-
* OpenSSL SSL object.
66-
*/
67-
SSL *ssl;
68-
69-
/**
70-
* SSL_write() requires to be called again with the same arguments it was
71-
* previously called with in the event of an SSL_read/SSL_write situation
72-
*/
73-
size_t lastLen;
74-
75-
/** Whether the SSL layer requires read (possibly before a write) */
76-
int wantRead;
77-
78-
/**
79-
* Whether a write was requested prior to a read. If set, the write()
80-
* should resume whenever a read takes place, if possible
81-
*/
82-
int pendingWrite;
83-
} redisSSL;
84-
8562
/* Forward declaration */
8663
redisContextFuncs redisContextSSLFuncs;
8764

@@ -163,6 +140,22 @@ int redisInitOpenSSL(void)
163140
return REDIS_OK;
164141
}
165142

143+
static int maybeCheckWant(redisSSL *rssl, int rv) {
144+
/**
145+
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
146+
* and true is returned. False is returned otherwise
147+
*/
148+
if (rv == SSL_ERROR_WANT_READ) {
149+
rssl->wantRead = 1;
150+
return 1;
151+
} else if (rv == SSL_ERROR_WANT_WRITE) {
152+
rssl->pendingWrite = 1;
153+
return 1;
154+
} else {
155+
return 0;
156+
}
157+
}
158+
166159
/**
167160
* redisSSLContext helper context destruction.
168161
*/
@@ -261,6 +254,42 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
261254
return NULL;
262255
}
263256

257+
int redisInitiateSSLContinue(redisContext *c) {
258+
if (!c->privctx) {
259+
__redisSetError(c, REDIS_ERR_OTHER, "redisContext is not associated");
260+
return REDIS_ERR;
261+
}
262+
263+
redisSSL *rssl = (redisSSL *)c->privctx;
264+
ERR_clear_error();
265+
int rv = SSL_connect(rssl->ssl);
266+
if (rv == 1) {
267+
c->privctx = rssl;
268+
return REDIS_OK;
269+
}
270+
271+
rv = SSL_get_error(rssl->ssl, rv);
272+
if (((c->flags & REDIS_BLOCK) == 0) &&
273+
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
274+
maybeCheckWant(rssl, rv);
275+
c->privctx = rssl;
276+
return REDIS_OK;
277+
}
278+
279+
if (c->err == 0) {
280+
char err[512];
281+
if (rv == SSL_ERROR_SYSCALL)
282+
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
283+
else {
284+
unsigned long e = ERR_peek_last_error();
285+
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
286+
ERR_reason_error_string(e));
287+
}
288+
__redisSetError(c, REDIS_ERR_IO, err);
289+
}
290+
return REDIS_ERR;
291+
}
292+
264293
/**
265294
* SSL Connection initialization.
266295
*/
@@ -295,6 +324,7 @@ static int redisSSLConnect(redisContext *c, SSL *ssl) {
295324
rv = SSL_get_error(rssl->ssl, rv);
296325
if (((c->flags & REDIS_BLOCK) == 0) &&
297326
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
327+
maybeCheckWant(rssl, rv);
298328
c->privctx = rssl;
299329
return REDIS_OK;
300330
}
@@ -315,6 +345,10 @@ static int redisSSLConnect(redisContext *c, SSL *ssl) {
315345
return REDIS_ERR;
316346
}
317347

348+
redisSSL *redisGetSSLSocket(redisContext *c) {
349+
return c->privctx;
350+
}
351+
318352
/**
319353
* A wrapper around redisSSLConnect() for users who manage their own context and
320354
* create their own SSL object.
@@ -361,22 +395,6 @@ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
361395
return REDIS_ERR;
362396
}
363397

364-
static int maybeCheckWant(redisSSL *rssl, int rv) {
365-
/**
366-
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
367-
* and true is returned. False is returned otherwise
368-
*/
369-
if (rv == SSL_ERROR_WANT_READ) {
370-
rssl->wantRead = 1;
371-
return 1;
372-
} else if (rv == SSL_ERROR_WANT_WRITE) {
373-
rssl->pendingWrite = 1;
374-
return 1;
375-
} else {
376-
return 0;
377-
}
378-
}
379-
380398
/**
381399
* Implementation of redisContextFuncs for SSL connections.
382400
*/

test/redis_client/connection_test.rb

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,6 @@ class SSLConnectionTest < Minitest::Test
218218
include ClientTestHelper
219219
include ConnectionTests
220220

221-
if ENV["DRIVER"] == "hiredis"
222-
def test_tcp_connect_downstream_timeout
223-
skip "TODO: Find the proper way to timeout SSL connections with hiredis"
224-
end
225-
226-
def test_tcp_connect_upstream_timeout
227-
skip "TODO: Find the proper way to timeout SSL connections with hiredis"
228-
end
229-
end
230-
231221
private
232222

233223
def new_client(**overrides)

0 commit comments

Comments
 (0)