diff --git a/doc/html/man5/3proxy.cfg.5.html b/doc/html/man5/3proxy.cfg.5.html
index 59c4109..c6955f6 100644
--- a/doc/html/man5/3proxy.cfg.5.html
+++ b/doc/html/man5/3proxy.cfg.5.html
@@ -1380,6 +1380,16 @@ ssl_client_mode mode - when to establish TLS
connection: 0 - on connect (default), 1 - after
authentication, 2 - before data, 3 - only for secure parent
types (ending with ’s’)
+ssl_ktls - use kernel TLS (Linux, FreeBSD) for the
+connections of the services which follow, where the TLS
+library, the kernel and the negotiated cipher allow it: the
+kernel then encrypts and decrypts the data; ssl_noktls
+(default) keeps it in 3proxy. Needs OpenSSL 3.0 or later built
+with kernel TLS (enable-ktls) and, on Linux, the tls kernel
+module; receiving with TLS 1.3 needs a later OpenSSL than 3.0.
+Whether the kernel took sending and receiving is logged on the
+first TLS connection of a service. Not supported with wolfSSL.
+
ssl_certcache /path/to/cache/ - location for the
generated MITM certificates cache
diff --git a/man/3proxy.cfg.5 b/man/3proxy.cfg.5
index ba97060..b0bddbd 100644
--- a/man/3proxy.cfg.5
+++ b/man/3proxy.cfg.5
@@ -1588,6 +1588,15 @@ SSL/TLS can be used for:
.BR ssl_client_mode
\fImode\fR - when to establish TLS connection: 0 - on connect (default), 1 - after authentication, 2 - before data, 3 - only for secure parent types (ending with 's')
.br
+.BR ssl_ktls
+- use kernel TLS (Linux, FreeBSD) for the connections of the services which
+follow, where the TLS library, the kernel and the negotiated cipher allow it:
+the kernel then encrypts and decrypts the data; \fBssl_noktls\fR (default)
+keeps it in 3proxy. Needs OpenSSL 3.0 or later built with kernel TLS
+(enable-ktls) and, on Linux, the tls kernel module; receiving with TLS 1.3
+needs a later OpenSSL than 3.0. Whether the kernel took sending and receiving is
+logged on the first TLS connection of a service. Not supported with wolfSSL.
+.br
.BR ssl_certcache
\fI/path/to/cache/\fR - location for the generated MITM certificates cache
diff --git a/src/conf.c b/src/conf.c
index 6176ab5..e8565a2 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -2046,6 +2046,8 @@ int h_no_client_verify(int argc, unsigned char **argv);
int h_server_verify(int argc, unsigned char **argv);
int h_no_server_verify(int argc, unsigned char **argv);
int h_client_mode(int argc, unsigned char **argv);
+int h_ktls(int argc, unsigned char **argv);
+int h_noktls(int argc, unsigned char **argv);
#endif
#ifdef WITH_HTTPSRV
int h_http_content_type(int argc, unsigned char **argv);
@@ -2183,6 +2185,8 @@ struct commands commandhandlers[]={
{NULL, "ssl_client_sni", h_client_sni, 1, 2},
{NULL, "ssl_client_alpn", h_client_alpn, 1, 0},
{NULL, "ssl_client_mode", h_client_mode, 1, 2},
+ {NULL, "ssl_ktls", h_ktls, 1, 1},
+ {NULL, "ssl_noktls", h_noktls, 1, 1},
{NULL, "ssl_certcache", h_certcache, 2, 2},
#endif
#ifdef WITH_TRANSPARENT
diff --git a/src/ssl.c b/src/ssl.c
index 1ed12b4..f7547e3 100644
--- a/src/ssl.c
+++ b/src/ssl.c
@@ -69,6 +69,7 @@ static char * client_cipher_list = NULL;
static char * server_cipher_list = NULL;
static char * client_sni = NULL;
static int client_mode = 0;
+static int ktls = 0;
struct SSLsock {
@@ -393,6 +394,32 @@ SSL_CONN ssl_handshake_to_client(SOCKET s, SSL_CONFIG *config, X509 *server_cert
#define PCONF (((struct SSLstate *)param->sostate)->config)
+/* After a handshake: read ahead, unless the kernel took receiving (kernel TLS
+ reads a record at a time), and say once for the service whether it took
+ either direction, which depends on the library, the kernel and the cipher. */
+static void ssl_after_handshake(struct clientparam* param, SSL_CONN conn){
+ SSL *ssl = ((ssl_conn *)conn)->ssl;
+ int ksend = 0, krecv = 0;
+
+ SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
+#if defined(SSL_OP_ENABLE_KTLS) && defined(BIO_get_ktls_send) && defined(BIO_get_ktls_recv)
+ if(PCONF->ktls){
+ ksend = BIO_get_ktls_send(SSL_get_wbio(ssl));
+ krecv = BIO_get_ktls_recv(SSL_get_rbio(ssl));
+ if(!PCONF->ktls_reported){
+ char buf[96];
+
+ PCONF->ktls_reported = 1;
+ sprintf(buf, "kernel TLS: sending %s, receiving %s",
+ ksend? "in the kernel" : "in 3proxy", krecv? "in the kernel" : "in 3proxy");
+ param->srv->logfunc(param, (unsigned char *)buf);
+ }
+ }
+#endif
+ if(!krecv) SSL_set_read_ahead(ssl, SSL_READAHEAD);
+ (void)ksend;
+}
+
SSL_CONN dosrvcon(struct clientparam* param, SSL_CERT* cert){
SSL_CONN ServerConn;
char *errSSL=NULL;
@@ -408,8 +435,7 @@ SSL_CONN dosrvcon(struct clientparam* param, SSL_CERT* cert){
return NULL;
}
- SSL_set_mode((SSL *)((ssl_conn *)ServerConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
- SSL_set_read_ahead((SSL *)((ssl_conn *)ServerConn)->ssl, SSL_READAHEAD);
+ ssl_after_handshake(param, ServerConn);
return ServerConn;
}
@@ -441,8 +467,7 @@ int domitm(struct clientparam* param){
return 3;
}
- SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
- SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_READAHEAD);
+ ssl_after_handshake(param, ClientConn);
addSSL(param->clisock, ClientConn, param->remsock, ServerConn, param);
@@ -549,6 +574,9 @@ SSL_CTX * ssl_cli_ctx(SSL_CONFIG *config, X509 *server_cert, EVP_PKEY *server_ke
}
}
SSL_CTX_set_session_id_context(ctx, (const unsigned char *)"3proxy", 6);
+#ifdef SSL_OP_ENABLE_KTLS
+ if(config->ktls) SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS);
+#endif
if(config->server_min_proto_version)SSL_CTX_set_min_proto_version(ctx, config->server_min_proto_version);
if(config->server_max_proto_version)SSL_CTX_set_max_proto_version(ctx, config->server_max_proto_version);
if(config->server_cipher_list)SSL_CTX_set_cipher_list(ctx, config->server_cipher_list);
@@ -589,6 +617,7 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){
sc->server_min_proto_version = server_min_proto_version;
sc->server_max_proto_version = server_max_proto_version;
sc->client_mode = client_mode;
+ sc->ktls = ktls;
sc->timeout = srv->timeouts[CONNECT_TO];
sc->client_verify = client_verify;
sc->server_verify = server_verify;
@@ -707,6 +736,9 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){
fprintf(stderr, "failed to set client context\n");
sc->mitm = sc->cli = 0;
}
+#ifdef SSL_OP_ENABLE_KTLS
+ if(sc->srv_ctx && sc->ktls) SSL_CTX_set_options(sc->srv_ctx, SSL_OP_ENABLE_KTLS);
+#endif
if(sc->client_cert){
SSL_CTX_use_certificate(sc->srv_ctx, (X509 *) sc->client_cert);
ssl_ctx_use_pkey(sc->srv_ctx, sc->client_key);
@@ -774,8 +806,7 @@ static FILTER_ACTION ssl_filter_client(void *fo, struct clientparam * param, voi
if(err)param->srv->logfunc(param, (unsigned char *)err);
return REJECT;
}
- SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
- SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_READAHEAD);
+ ssl_after_handshake(param, ClientConn);
addSSL(param->clisock, ClientConn, INVALID_SOCKET, NULL, param);
}
return CONTINUE;
@@ -1184,6 +1215,20 @@ int h_no_server_verify(int argc, unsigned char **argv){
return 0;
}
+int h_ktls(int argc, unsigned char **argv){
+#ifdef SSL_OP_ENABLE_KTLS
+ ktls = 1;
+#else
+ fprintf(stderr, "ssl_ktls: kernel TLS is not supported by this TLS library, ignored\n");
+#endif
+ return 0;
+}
+
+int h_noktls(int argc, unsigned char **argv){
+ ktls = 0;
+ return 0;
+}
+
static struct symbol ssl_symbols[] = {
{NULL, "ssl_parent", (void *)&ssl_parent},
};
@@ -1214,6 +1259,7 @@ void ssl_install(void){
client_verify = 0;
server_verify = 0;
client_mode = 0;
+ ktls = 0;
free(client_ciphersuites);
client_ciphersuites = NULL;
free(server_ciphersuites);
diff --git a/src/ssl.h b/src/ssl.h
index 59fe580..4df3679 100644
--- a/src/ssl.h
+++ b/src/ssl.h
@@ -52,6 +52,8 @@ struct ssl_config {
int client_verify;
int server_verify;
int client_mode;
+ int ktls; /* ssl_ktls: kernel TLS asked for (SSL_OP_ENABLE_KTLS) */
+ int ktls_reported; /* whether the kernel took it has been logged */
int timeout; /* seconds a handshake step may wait: the service's CONNECT_TO */
};
diff --git a/tests/cases/ktls.py b/tests/cases/ktls.py
new file mode 100644
index 0000000..4555bce
--- /dev/null
+++ b/tests/cases/ktls.py
@@ -0,0 +1,76 @@
+"""Kernel TLS (ssl_ktls): a TLS chain works the same whether or not the
+kernel takes the encryption, and the proxy says which it did.
+
+Whether it does depends on the TLS library (OpenSSL built with enable-ktls),
+the kernel (the tls module on Linux) and the cipher, so the case checks the
+data and the report, not that the kernel took it.
+"""
+
+
+def run(t):
+ certs = t.certs()
+ if not certs:
+ t.skip("kernel TLS (openssl is not available to generate certificates)")
+ return
+ if not certs.verified:
+ t.fail("the generated certificate chain verifies", "OK",
+ certs.verify_output or "openssl verify failed")
+ return
+
+ origin = t.free_port()
+ parent = t.free_port()
+ client = t.free_port()
+
+ server = t.start("ktls", f"""
+ log
+ auth iponly
+ allow *
+ http echo * /echo**
+ http data * /data
+ httpsrv -p{origin}
+
+ flush
+ ssl_ktls
+ ssl_server_cert {certs.server}
+ ssl_server_key {certs.server_key}
+ ssl_serv
+ auth iponly
+ allow *
+ proxy -p{parent}
+
+ flush
+ ssl_noserv
+ auth iponly
+ allow *
+ parent 1000 connects 127.0.0.1 {parent}
+ ssl_client_mode 3
+ ssl_client_ca_file {certs.ca}
+ ssl_client_verify
+ ssl_cli
+ proxy -p{client}
+ """, ports=[origin, parent, client])
+
+ out = server.output()
+ if "Unknown command" in out:
+ t.skip("kernel TLS (this build has no SSL support)")
+ return
+
+ through = f"127.0.0.1:{client}"
+ r = t.http(f"http://127.0.0.1:{origin}/echo", proxy=through)
+ t.eq(200, r.status, "a request through the TLS chain with ssl_ktls arrives")
+ t.contains(r, "path=/echo", "the origin sees it")
+
+ # many records, both ways through both TLS legs
+ for size in (100000, 3000000):
+ t.eq(size, t.http(f"http://127.0.0.1:{origin}/data?size={size}",
+ proxy=through).length,
+ f"a {size} byte body survives the kernel TLS chain")
+ t.eq(200000, t.http(f"http://127.0.0.1:{origin}/data?size=200000&chunked=1",
+ proxy=through).length,
+ "a chunked body survives the kernel TLS chain")
+
+ if "not supported by this TLS library" in server.output():
+ t.skip("the kernel TLS report (this TLS library has no kernel TLS)")
+ return
+ log = t.wait_output(server, "kernel TLS:")
+ t.contains(log, "kernel TLS: sending", "the proxy says whether the kernel took TLS")