From 587acbc53a33c34023569df5349b5bb792450d9a Mon Sep 17 00:00:00 2001
From: Grant Zhang <gzhang@fastly.com>
Date: Sat, 14 Jan 2017 01:42:15 +0000
Subject: [PATCH 2/2] RFC: add openssl async support

ssl_async is a global configuration parameter which enables asynchronous
processing in OPENSSL for all SSL connections haproxy handles. With
SSL_MODE_ASYNC mode set, TLS I/O operations may indicate a retry with
SSL_ERROR_WANT_ASYNC with this mode set if an asynchronous capable
engine is used to perform cryptographic operations.
---
 doc/configuration.txt      |    5 ++
 include/types/connection.h |    2 +
 include/types/fd.h         |    1 +
 src/ssl_sock.c             |  112 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 120 insertions(+)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index a2b4fef..ad68657 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -592,6 +592,7 @@ The following keywords are supported in the "global" section :
    - server-state-base
    - server-state-file
    - ssl-engine
+   - ssl-async
    - tune.buffers.limit
    - tune.buffers.reserve
    - tune.bufsize
@@ -1281,6 +1282,10 @@ ssl-engine <name> [algo <comma-seperated list of algorithms>]
   openssl configuration file uses:
   https://www.openssl.org/docs/man1.0.2/apps/config.html
 
+ssl-async
+  Adds SSL_MODE_ASYNC mode to the SSL context. This enables asynchronous TLS
+  I/O operations if an asynchronous capable SSL engine is used.
+
 tune.buffers.limit <number>
   Sets a hard limit on the number of buffers which may be allocated per process.
   The default value is zero which means unlimited. The minimum non-zero value
diff --git a/include/types/connection.h b/include/types/connection.h
index 9d1b51a..03ae33e 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -303,6 +303,8 @@ struct connection {
 		struct sockaddr_storage from;	/* client address, or address to spoof when connecting to the server */
 		struct sockaddr_storage to;	/* address reached by the client, or address to connect to */
 	} addr; /* addresses of the remote side, client for producer and server for consumer */
+
+	OSSL_ASYNC_FD async_fd;
 };
 
 /* proxy protocol v2 definitions */
diff --git a/include/types/fd.h b/include/types/fd.h
index 2bd7c07..42808eb 100644
--- a/include/types/fd.h
+++ b/include/types/fd.h
@@ -100,6 +100,7 @@ struct fdtab {
 	unsigned char updated:1;             /* 1 if this fd is already in the update list */
 	unsigned char linger_risk:1;         /* 1 if we must kill lingering before closing */
 	unsigned char cloned:1;              /* 1 if a cloned socket, requires EPOLL_CTL_DEL on close */
+	unsigned char async:1;               /* 1 if this fd is async ssl fd */
 };
 
 /* less often used information */
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index b216746..0b14824 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -53,6 +53,7 @@
 #include <openssl/dh.h>
 #endif
 #include <openssl/engine.h>
+#include <openssl/async.h>
 
 #include <import/lru.h>
 #include <import/xxhash.h>
@@ -136,6 +137,7 @@ static struct xprt_ops ssl_sock;
 static struct {
 	char *crt_base;             /* base directory path for certificates */
 	char *ca_base;              /* base directory path for CAs and CRLs */
+	int  async;                 /* whether we use ssl async mode */
 
 	char *listen_default_ciphers;
 	char *connect_default_ciphers;
@@ -326,6 +328,54 @@ static int ssl_init_engines(void)
 	return err_code;
 }
 
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+void ssl_async_fd_handler(int fd)
+{
+	struct connection *conn = fdtab[fd].owner;
+	int conn_fd = conn->t.sock.fd;
+
+	/* crypto engine is available, let's notify the associated
+	 * connection that it can pursue its processing.
+	 */
+	conn_fd_handler(conn_fd);
+}
+
+/*
+ * openssl async fd handler
+ */
+void ssl_async_process_fds(struct connection *conn)
+{
+	OSSL_ASYNC_FD add_fd;
+	OSSL_ASYNC_FD del_fd;
+	size_t num_add_fds = 0;
+	size_t num_del_fds = 0;
+	SSL *ssl = conn->xprt_ctx;
+
+	SSL_get_changed_async_fds(ssl, &add_fd, &num_add_fds, &del_fd,
+			&num_del_fds);
+
+	if (num_add_fds == 0 && num_del_fds == 0)
+		return;
+
+	/* we don't support more than 1 async fds */
+	if (num_add_fds > 1 || num_del_fds > 1)
+		return;
+
+	if (num_del_fds)
+		fd_stop_both(del_fd);
+
+	if (num_add_fds) {
+		conn->async_fd = add_fd;
+		fdtab[add_fd].async = 1;
+		fdtab[add_fd].state = 0;
+		fdtab[add_fd].owner = conn;
+		fdtab[add_fd].iocb = ssl_async_fd_handler;
+		fd_insert(add_fd);
+		fd_want_recv(add_fd);
+	}
+}
+#endif
+
 /*
  *  This function returns the number of seconds  elapsed
  *  since the Epoch, 1970-01-01 00:00:00 +0000 (UTC) and the
@@ -3280,6 +3330,11 @@ ssl_sock_initial_ctx(struct bind_conf *bind_conf)
 	if (conf_ssl_options & BC_SSL_O_NO_TLS_TICKETS)
 		ssloptions |= SSL_OP_NO_TICKET;
 	SSL_CTX_set_options(ctx, ssloptions);
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+	if (global_ssl.async)
+		sslmode |= SSL_MODE_ASYNC;
+#endif
 	SSL_CTX_set_mode(ctx, sslmode);
 	if (global_ssl.life_time)
 		SSL_CTX_set_timeout(ctx, global_ssl.life_time);
@@ -3687,6 +3742,10 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
 		return cfgerr;
 	}
 	SSL_CTX_set_options(ctx, options);
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+	if (global_ssl.async)
+		mode |= SSL_MODE_ASYNC;
+#endif
 	SSL_CTX_set_mode(ctx, mode);
 	srv->ssl_ctx.ctx = ctx;
 
@@ -4175,6 +4234,14 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag)
 		if (ret <= 0) {
 			/* handshake may have not been completed, let's find why */
 			ret = SSL_get_error(conn->xprt_ctx, ret);
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+			if (ret == SSL_ERROR_WANT_ASYNC) {
+				ssl_async_process_fds(conn);
+				return 0;
+			}
+#endif
+
 			if (ret == SSL_ERROR_WANT_WRITE) {
 				/* SSL handshake needs to write, L4 connection may not be ready */
 				__conn_sock_stop_recv(conn);
@@ -4260,6 +4327,12 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag)
 		/* handshake did not complete, let's find why */
 		ret = SSL_get_error(conn->xprt_ctx, ret);
 
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+		if (ret == SSL_ERROR_WANT_ASYNC) {
+			ssl_async_process_fds(conn);
+			return 0;
+		}
+#endif
 		if (ret == SSL_ERROR_WANT_WRITE) {
 			/* SSL handshake needs to write, L4 connection may not be ready */
 			__conn_sock_stop_recv(conn);
@@ -4445,6 +4518,12 @@ static int ssl_sock_to_buf(struct connection *conn, struct buffer *buf, int coun
 		}
 		else {
 			ret =  SSL_get_error(conn->xprt_ctx, ret);
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+			if (ret == SSL_ERROR_WANT_ASYNC) {
+				ssl_async_process_fds(conn);
+				break;
+			}
+#endif
 			if (ret == SSL_ERROR_WANT_WRITE) {
 				/* handshake is running, and it needs to enable write */
 				conn->flags |= CO_FL_SSL_WAIT_HS;
@@ -4546,6 +4625,13 @@ static int ssl_sock_from_buf(struct connection *conn, struct buffer *buf, int fl
 		}
 		else {
 			ret = SSL_get_error(conn->xprt_ctx, ret);
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+			if (ret == SSL_ERROR_WANT_ASYNC) {
+				ssl_async_process_fds(conn);
+				break;
+			}
+#endif
 			if (ret == SSL_ERROR_WANT_WRITE) {
 				if (SSL_renegotiate_pending(conn->xprt_ctx)) {
 					/* handshake is running, and it may need to re-enable write */
@@ -4580,6 +4666,15 @@ static int ssl_sock_from_buf(struct connection *conn, struct buffer *buf, int fl
 static void ssl_sock_close(struct connection *conn) {
 
 	if (conn->xprt_ctx) {
+		if (global_ssl.async) {
+			/* the async fd is created and owned by the SSL engine, which is
+			 * responsible for fd closure. Here we are done with the async fd
+			 * thus disable the polling on it, as well as clean up fdtab entry.
+			 */
+			fd_stop_both(conn->async_fd);
+			fdtab[conn->async_fd].async = 0;
+			fdtab[conn->async_fd].state = 0;
+		}
 		SSL_free(conn->xprt_ctx);
 		conn->xprt_ctx = NULL;
 		sslconns--;
@@ -6974,6 +7069,22 @@ static int ssl_parse_global_ca_crt_base(char **args, int section_type, struct pr
 	return 0;
 }
 
+/* parse the "ssl-async" keyword in global section.
+ * Returns <0 on alert, >0 on warning, 0 on success.
+ */
+static int ssl_parse_global_ssl_async(char **args, int section_type, struct proxy *curpx,
+                                       struct proxy *defpx, const char *file, int line,
+                                       char **err)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+	global_ssl.async = 1;
+	return 0;
+#else
+	memprintf(err, "'%s': openssl library does not support async mode", args[0]);
+	return -1;
+#endif
+}
+
 /* parse the "ssl-engine" keyword in global section.
  * Returns <0 on alert, >0 on warning, 0 on success.
  */
@@ -7623,6 +7734,7 @@ static struct cfg_kw_list cfg_kws = {ILH, {
 #ifndef OPENSSL_NO_DH
 	{ CFG_GLOBAL, "ssl-dh-param-file", ssl_parse_global_dh_param_file },
 #endif
+	{ CFG_GLOBAL, "ssl-async",  ssl_parse_global_ssl_async },
 	{ CFG_GLOBAL, "ssl-engine",  ssl_parse_global_ssl_engine },
 	{ CFG_GLOBAL, "tune.ssl.cachesize", ssl_parse_global_int },
 #ifndef OPENSSL_NO_DH
-- 
1.7.9.5

