3proxy/tests/cases/ktls.py
Vladimir Dubrovin d0e88a7b88 Optional kernel TLS with OpenSSL (ssl_ktls)
ssl_ktls asks OpenSSL to hand TLS to the kernel (SSL_OP_ENABLE_KTLS)
on the connections of the services which follow, both to clients and
to servers; ssl_noktls, the default, keeps it in 3proxy. Where the
library, the kernel and the cipher allow it, the kernel then encrypts
and decrypts, and SSL_read/SSL_write go on working over it. What a
service got is logged on its first TLS connection.

Reading ahead is left off where the kernel took receiving, which reads
a record at a time. A build whose library has no kernel TLS (wolfSSL,
OpenSSL before 3.0 or without it) warns and ignores the command.
splice() is not used for TLS services yet.

Tested on AlmaLinux 10 (OpenSSL 3.5.8, tls module): both directions
of both legs of a TLS chain in the kernel, the suite passing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:29:21 +03:00

77 lines
2.5 KiB
Python

"""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")