MEDIUM: ssl: Handle subscribe by itself.
As the SSL code may have different needs than the upper layer, ie it may want
to receive when the upper layer wants to right, instead of directly forwarding
the subscribe to the underlying xprt, handle it ourself. The SSL code will
know remember any subscribe call, and wake the tasklet when it is ready
for more I/O.
diff --git a/include/types/connection.h b/include/types/connection.h
index 7e253e0..e6b3ed5 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -192,6 +192,7 @@
/* below we have all handshake flags grouped into one */
CO_FL_HANDSHAKE = CO_FL_SEND_PROXY | CO_FL_SSL_WAIT_HS | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
+ CO_FL_HANDSHAKE_NOSSL = CO_FL_SEND_PROXY | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
/* when any of these flags is set, polling is defined by socket-layer
* operations, as opposed to data-layer. Transport is explicitly not
diff --git a/src/connection.c b/src/connection.c
index bb0c3b3..1c81a0c 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -90,11 +90,16 @@
if (conn->flags & CO_FL_SEND_PROXY)
if (!conn_si_send_proxy(conn, CO_FL_SEND_PROXY))
goto leave;
-#ifdef USE_OPENSSL
- if (conn->flags & CO_FL_SSL_WAIT_HS)
- if (!ssl_sock_handshake(conn, CO_FL_SSL_WAIT_HS))
- goto leave;
-#endif
+ /* sock polling may have been activated by the connection,
+ * so remove it if we don't want it.
+ */
+ if (conn->flags & CO_FL_SSL_WAIT_HS) {
+ if (!conn->send_wait)
+ __conn_sock_stop_send(conn);
+ if (!conn->recv_wait)
+ __conn_sock_stop_recv(conn);
+ break;
+ }
}
/* Once we're purely in the data phase, we disable handshake polling */
@@ -108,7 +113,8 @@
* leave instead. The caller must immediately unregister itself once
* called.
*/
- if (conn->xprt_done_cb && conn->xprt_done_cb(conn) < 0)
+ if (!(conn->flags & CO_FL_SSL_WAIT_HS) &&
+ conn->xprt_done_cb && conn->xprt_done_cb(conn) < 0)
return;
if (conn->xprt && fd_send_ready(fd)) {
@@ -148,7 +154,7 @@
/* It may happen during the data phase that a handshake is
* enabled again (eg: SSL)
*/
- if (unlikely(conn->flags & (CO_FL_HANDSHAKE | CO_FL_ERROR)))
+ if (unlikely(conn->flags & (CO_FL_HANDSHAKE_NOSSL | CO_FL_ERROR)))
goto process_handshake;
if (unlikely(conn->flags & CO_FL_WAIT_L4_CONN)) {
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index a007e9a..cc6f3b5 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -213,6 +213,9 @@
BIO *bio;
struct xprt_ops *xprt;
void *xprt_ctx;
+ struct wait_event wait_event;
+ struct wait_event *recv_wait;
+ struct wait_event *send_wait;
int xprt_st; /* transport layer state, initialized to zero */
int tmp_early_data; /* 1st byte of early data, if any */
int sent_early_data; /* Amount of early data we sent so far */
@@ -221,6 +224,8 @@
DECLARE_STATIC_POOL(ssl_sock_ctx_pool, "ssl_sock_ctx_pool", sizeof(struct ssl_sock_ctx));
+static struct task *ssl_sock_io_cb(struct task *, void *, unsigned short);
+
/* Methods to implement OpenSSL BIO */
static int ha_ssl_write(BIO *h, const char *buf, int num)
{
@@ -535,7 +540,8 @@
*/
void ssl_async_fd_handler(int fd)
{
- struct connection *conn = fdtab[fd].owner;
+ struct ssl_sock_ctx *ctx = fdtab[fd].owner;
+ struct connection *conn = ctx->conn;
/* fd is an async enfine fd, we must stop
* to poll this fd until it is requested
@@ -585,10 +591,12 @@
* function used to manage a returned SSL_ERROR_WANT_ASYNC
* and enable/disable polling for async fds
*/
-static inline void ssl_async_process_fds(struct connection *conn, SSL *ssl)
+static inline void ssl_async_process_fds(struct ssl_sock_ctx *ctx)
{
OSSL_ASYNC_FD add_fd[32];
OSSL_ASYNC_FD del_fd[32];
+ SSL *ssl = ctx->ssl;
+ struct connection *conn = ctx->conn;
size_t num_add_fds = 0;
size_t num_del_fds = 0;
int i;
@@ -608,7 +616,7 @@
/* We add new fds to the fdtab */
for (i=0 ; i < num_add_fds ; i++) {
- fd_insert(add_fd[i], conn, ssl_async_fd_handler, tid_bit);
+ fd_insert(add_fd[i], ctx, ssl_async_fd_handler, tid_bit);
}
num_add_fds = 0;
@@ -5110,6 +5118,15 @@
conn->err_code = CO_ER_SSL_NO_MEM;
return -1;
}
+ ctx->wait_event.task = tasklet_new();
+ if (!ctx->wait_event.task) {
+ conn->err_code = CO_ER_SSL_NO_MEM;
+ pool_free(ssl_sock_ctx_pool, ctx);
+ return -1;
+ }
+ ctx->wait_event.task->process = ssl_sock_io_cb;
+ ctx->wait_event.task->context = ctx;
+ ctx->wait_event.events = 0;
ctx->sent_early_data = 0;
ctx->tmp_early_data = -1;
ctx->conn = conn;
@@ -5119,15 +5136,13 @@
*/
ctx->xprt = xprt_get(XPRT_RAW);
if (ctx->xprt->init) {
- if (ctx->xprt->init(conn, &ctx->xprt_ctx) != 0) {
- pool_free(ssl_sock_ctx_pool, ctx);
- return -1;
- }
+ if (ctx->xprt->init(conn, &ctx->xprt_ctx) != 0)
+ goto err;
}
if (global.maxsslconn && sslconns >= global.maxsslconn) {
conn->err_code = CO_ER_SSL_TOO_MANY;
- return -1;
+ goto err;
}
/* If it is in client mode initiate SSL session
@@ -5190,6 +5205,10 @@
_HA_ATOMIC_ADD(&sslconns, 1);
_HA_ATOMIC_ADD(&totalsslconns, 1);
*xprt_ctx = ctx;
+ /* Start the handshake */
+ tasklet_wakeup(ctx->wait_event.task);
+ if (conn->flags & CO_FL_ERROR)
+ goto err;
return 0;
}
else if (objt_listener(conn->target)) {
@@ -5242,11 +5261,17 @@
_HA_ATOMIC_ADD(&sslconns, 1);
_HA_ATOMIC_ADD(&totalsslconns, 1);
*xprt_ctx = ctx;
+ /* Start the handshake */
+ tasklet_wakeup(ctx->wait_event.task);
+ if (conn->flags & CO_FL_ERROR)
+ goto err;
return 0;
}
/* don't know how to handle such a target */
conn->err_code = CO_ER_SSL_NO_TARGET;
err:
+ if (ctx && ctx->wait_event.task)
+ tasklet_free(ctx->wait_event.task);
pool_free(ssl_sock_ctx_pool, ctx);
return -1;
}
@@ -5305,9 +5330,10 @@
if (ret == SSL_ERROR_WANT_WRITE) {
/* SSL handshake needs to write, L4 connection may not be ready */
- __conn_sock_stop_recv(conn);
- __conn_sock_want_send(conn);
- fd_cant_send(conn->handle.fd);
+ if (!(ctx->wait_event.events & SUB_RETRY_SEND)) {
+ __conn_sock_want_send(conn);
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND, &ctx->wait_event);
+ }
return 0;
}
else if (ret == SSL_ERROR_WANT_READ) {
@@ -5319,16 +5345,15 @@
goto reneg_ok;
}
/* SSL handshake needs to read, L4 connection is ready */
- if (conn->flags & CO_FL_WAIT_L4_CONN)
- conn->flags &= ~CO_FL_WAIT_L4_CONN;
- __conn_sock_stop_send(conn);
- __conn_sock_want_recv(conn);
- fd_cant_recv(conn->handle.fd);
+ if (!(ctx->wait_event.events & SUB_RETRY_RECV)) {
+ __conn_sock_want_recv(conn);
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV, &ctx->wait_event);
+ }
return 0;
}
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1010000fL) && !defined(OPENSSL_NO_ASYNC)
else if (ret == SSL_ERROR_WANT_ASYNC) {
- ssl_async_process_fds(conn, ctx->ssl);
+ ssl_async_process_fds(ctx);
return 0;
}
#endif
@@ -5396,23 +5421,25 @@
if (ret == SSL_ERROR_WANT_WRITE) {
/* SSL handshake needs to write, L4 connection may not be ready */
- __conn_sock_stop_recv(conn);
- __conn_sock_want_send(conn);
- fd_cant_send(conn->handle.fd);
+ if (!(ctx->wait_event.events & SUB_RETRY_SEND)) {
+ __conn_sock_want_send(conn);
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND, &ctx->wait_event);
+ }
return 0;
}
else if (ret == SSL_ERROR_WANT_READ) {
/* SSL handshake needs to read, L4 connection is ready */
- if (conn->flags & CO_FL_WAIT_L4_CONN)
- conn->flags &= ~CO_FL_WAIT_L4_CONN;
- __conn_sock_stop_send(conn);
- __conn_sock_want_recv(conn);
- fd_cant_recv(conn->handle.fd);
+ if (!(ctx->wait_event.events & SUB_RETRY_RECV))
+ {
+ __conn_sock_want_recv(conn);
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx,
+ SUB_RETRY_RECV, &ctx->wait_event);
+ }
return 0;
}
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1010000fL) && !defined(OPENSSL_NO_ASYNC)
else if (ret == SSL_ERROR_WANT_ASYNC) {
- ssl_async_process_fds(conn, ctx->ssl);
+ ssl_async_process_fds(ctx);
return 0;
}
#endif
@@ -5538,16 +5565,117 @@
static int ssl_subscribe(struct connection *conn, void *xprt_ctx, int event_type, void *param)
{
+ struct wait_event *sw;
+ struct ssl_sock_ctx *ctx = xprt_ctx;
- return conn_subscribe(conn, NULL, event_type, param);
+ if (event_type & SUB_RETRY_RECV) {
+ sw = param;
+ BUG_ON(ctx->recv_wait != NULL || (sw->events & SUB_RETRY_RECV));
+ sw->events |= SUB_RETRY_RECV;
+ ctx->recv_wait = sw;
+ if (!(conn->flags & CO_FL_SSL_WAIT_HS) &&
+ !(ctx->wait_event.events & SUB_RETRY_RECV))
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV, &ctx->wait_event);
+ event_type &= ~SUB_RETRY_RECV;
+ }
+ if (event_type & SUB_RETRY_SEND) {
+sw = param;
+ BUG_ON(ctx->send_wait != NULL || (sw->events & SUB_RETRY_SEND));
+ sw->events |= SUB_RETRY_SEND;
+ ctx->send_wait = sw;
+ if (!(conn->flags & CO_FL_SSL_WAIT_HS) &&
+ !(ctx->wait_event.events & SUB_RETRY_SEND))
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND, &ctx->wait_event);
+ event_type &= ~SUB_RETRY_SEND;
+
+ }
+ if (event_type != 0)
+ return -1;
+ return 0;
}
static int ssl_unsubscribe(struct connection *conn, void *xprt_ctx, int event_type, void *param)
{
+ struct wait_event *sw;
+ struct ssl_sock_ctx *ctx = xprt_ctx;
- return conn_unsubscribe(conn, NULL, event_type, param);
+ if (event_type & SUB_RETRY_RECV) {
+ sw = param;
+ BUG_ON(ctx->recv_wait != sw);
+ ctx->recv_wait = NULL;
+ sw->events &= ~SUB_RETRY_RECV;
+ /* If we subscribed, and we're not doing the handshake,
+ * then we subscribed because the upper layer asked for it,
+ * as the upper layer is no longer interested, we can
+ * unsubscribe too.
+ */
+ if (!(ctx->conn->flags & CO_FL_SSL_WAIT_HS) &&
+ (ctx->wait_event.events & SUB_RETRY_RECV))
+ conn_unsubscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV,
+ &ctx->wait_event);
+ }
+ if (event_type & SUB_RETRY_SEND) {
+ sw = param;
+ BUG_ON(ctx->send_wait != sw);
+ ctx->send_wait = NULL;
+ sw->events &= ~SUB_RETRY_SEND;
+ if (!(ctx->conn->flags & CO_FL_SSL_WAIT_HS) &&
+ (ctx->wait_event.events & SUB_RETRY_SEND))
+ conn_unsubscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND,
+ &ctx->wait_event);
+
+ }
+
+ return 0;
}
+static struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned short state)
+{
+ struct ssl_sock_ctx *ctx = context;
+
+ /* First if we're doing an handshake, try that */
+ if (ctx->conn->flags & CO_FL_SSL_WAIT_HS)
+ ssl_sock_handshake(ctx->conn, CO_FL_SSL_WAIT_HS);
+ /* If we had an error, or the handshake is done and I/O is available,
+ * let the upper layer know.
+ * If no mux was set up yet, and nobody subscribed, then call
+ * xprt_done_cb() ourself if it's set, or destroy the connection,
+ * we can't be sure conn_fd_handler() will be called again.
+ */
+ if ((ctx->conn->flags & CO_FL_ERROR) ||
+ !(ctx->conn->flags & CO_FL_SSL_WAIT_HS)) {
+ int ret = 0;
+ int woke = 0;
+
+ /* On error, wake any waiter */
+ if (ctx->recv_wait) {
+ ctx->recv_wait->events &= ~SUB_RETRY_RECV;
+ tasklet_wakeup(ctx->recv_wait->task);
+ ctx->recv_wait = NULL;
+ woke = 1;
+ }
+ if (ctx->send_wait) {
+ ctx->send_wait->events &= ~SUB_RETRY_SEND;
+ tasklet_wakeup(ctx->send_wait->task);
+ ctx->send_wait = NULL;
+ woke = 1;
+ }
+ /* If we're the first xprt for the connection, let the
+ * upper layers know. If xprt_done_cb() is set, call it,
+ * otherwise, we should have a mux, so call its wake
+ * method if we didn't woke a tasklet already.
+ */
+ if (ctx->conn->xprt_ctx == ctx) {
+ if (ctx->conn->xprt_done_cb)
+ ret = ctx->conn->xprt_done_cb(ctx->conn);
+ if (ret >= 0 && !woke && ctx->conn->mux && ctx->conn->mux->wake)
+ ctx->conn->mux->wake(ctx->conn);
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
/* Receive up to <count> bytes from connection <conn>'s socket and store them
* into buffer <buf>. Only one call to recv() is performed, unless the
* buffer wraps, in which case a second call may be performed. The connection's
@@ -5649,6 +5777,7 @@
/* handshake is running, and it needs to enable write */
conn->flags |= CO_FL_SSL_WAIT_HS;
__conn_sock_want_send(conn);
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND, &ctx->wait_event);
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1010000fL) && !defined(OPENSSL_NO_ASYNC)
/* Async mode can be re-enabled, because we're leaving data state.*/
if (global_ssl.async)
@@ -5658,6 +5787,9 @@
}
else if (ret == SSL_ERROR_WANT_READ) {
if (SSL_renegotiate_pending(ctx->ssl)) {
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx,
+ SUB_RETRY_RECV,
+ &ctx->wait_event);
/* handshake is running, and it may need to re-enable read */
conn->flags |= CO_FL_SSL_WAIT_HS;
__conn_sock_want_recv(conn);
@@ -5668,8 +5800,6 @@
#endif
break;
}
- /* we need to poll for retry a read later */
- fd_cant_recv(conn->handle.fd);
break;
} else if (ret == SSL_ERROR_ZERO_RETURN)
goto read0;
@@ -5811,6 +5941,7 @@
/* handshake is running, and it may need to re-enable write */
conn->flags |= CO_FL_SSL_WAIT_HS;
__conn_sock_want_send(conn);
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND, &ctx->wait_event);
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1010000fL) && !defined(OPENSSL_NO_ASYNC)
/* Async mode can be re-enabled, because we're leaving data state.*/
if (global_ssl.async)
@@ -5818,14 +5949,16 @@
#endif
break;
}
- /* we need to poll to retry a write later */
- fd_cant_send(conn->handle.fd);
+
break;
}
else if (ret == SSL_ERROR_WANT_READ) {
/* handshake is running, and it needs to enable read */
conn->flags |= CO_FL_SSL_WAIT_HS;
__conn_sock_want_recv(conn);
+ ctx->xprt->subscribe(conn, ctx->xprt_ctx,
+ SUB_RETRY_RECV,
+ &ctx->wait_event);
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1010000fL) && !defined(OPENSSL_NO_ASYNC)
/* Async mode can be re-enabled, because we're leaving data state.*/
if (global_ssl.async)
@@ -5853,7 +5986,20 @@
struct ssl_sock_ctx *ctx = xprt_ctx;
+
if (ctx) {
+ if (ctx->wait_event.events != 0)
+ ctx->xprt->unsubscribe(ctx->conn, ctx->xprt_ctx,
+ ctx->wait_event.events,
+ &ctx->wait_event);
+ if (ctx->send_wait) {
+ ctx->send_wait->events &= ~SUB_RETRY_SEND;
+ tasklet_wakeup(ctx->send_wait->task);
+ }
+ if (ctx->recv_wait) {
+ ctx->recv_wait->events &= ~SUB_RETRY_RECV;
+ tasklet_wakeup(ctx->recv_wait->task);
+ }
if (ctx->xprt->close)
ctx->xprt->close(conn, ctx->xprt_ctx);
#if (HA_OPENSSL_VERSION_NUMBER >= 0x1010000fL) && !defined(OPENSSL_NO_ASYNC)
@@ -5887,6 +6033,7 @@
*/
fd_cant_recv(afd);
}
+ tasklet_free(ctx->wait_event.task);
pool_free(ssl_sock_ctx_pool, ctx);
_HA_ATOMIC_ADD(&jobs, 1);
return;
@@ -5902,6 +6049,7 @@
}
#endif
SSL_free(ctx->ssl);
+ tasklet_free(ctx->wait_event.task);
pool_free(ssl_sock_ctx_pool, ctx);
_HA_ATOMIC_SUB(&sslconns, 1);
}