Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions Playground/Clients/File/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideFile(ref dir, ref buffered, ref tlsMode);

Expand All @@ -70,7 +71,7 @@
Env.Override(ref port, ref reactors);
// ─────────────────────────────────────────────────────────────────────────────────────────────

// The constraint this sample exists to show, enforced instead of described: with OpenSSL owning
// The constraint this sample exists to show, enforced instead of described: with BoringSSL owning
// transmit the slab must hold CIPHERTEXT, so a file read straight into it would go out in the
// clear. There is no slab path for that backend - refuse rather than serve cleartext.
if (tlsMode == "openssl" && !buffered)
Expand Down Expand Up @@ -139,8 +140,8 @@

// The whole point: with transmit in the kernel the slab carries PLAINTEXT and the
// kernel makes the records, so ReadFileAsync may still read the file straight into
// it. With this false, OpenSSL encrypts and the slab must hold ciphertext - which
// is why the slab path is refused above. Receive is OpenSSL either way.
// it. With this false, BoringSSL encrypts and the slab must hold ciphertext - which
// is why the slab path is refused above. Receive is BoringSSL either way.
KernelTx = ktls,
});
}
Expand Down Expand Up @@ -333,9 +334,9 @@ static unsafe void WriteNative(TcpConnection conn, TlsSession? tls, nint data, i
=> Emit(conn, tls, new ReadOnlySpan<byte>((void*)data, length));

// Every response byte goes through here. TlsSession.Write is correct under BOTH backends - it
// writes plaintext to the slab when the kernel encrypts, and encrypts into the slab when OpenSSL
// writes plaintext to the slab when the kernel encrypts, and encrypts into the slab when BoringSSL
// does - so no call site has to know which one is in play. A bare conn.Write would be right only
// for cleartext and kTLS, and silently wrong for OpenSSL.
// for cleartext and kTLS, and silently wrong for BoringSSL.
static void Emit(TcpConnection conn, TlsSession? tls, ReadOnlySpan<byte> bytes)
{
if (tls is null) conn.Write(bytes);
Expand Down
1 change: 1 addition & 0 deletions Playground/Clients/GatewayTlsPipes/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
// A real PEM pair for the front door, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

(string certPath, string keyPath) = QuicCert.Ensure(certOverride, keyOverride);

Expand Down
4 changes: 2 additions & 2 deletions Playground/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ RUN csproj="$(ls Playground/${SAMPLE}/*.csproj)" \
&& dotnet publish "$csproj" -c Release -o /app \
&& ln -s "/app/$(basename "$csproj" .csproj)" /app/playground

# The ngtcp2 bundle's only external dependency is OpenSSL 3 (libcrypto.so.3), which the Debian-based
# runtime image already carries.
# The native bundles (BoringSSL, ngtcp2, nghttp2, nghttp3) depend on nothing beyond libc; the runtime
# image's own OpenSSL is .NET's, for its crypto APIs.
FROM mcr.microsoft.com/dotnet/runtime:11.0-preview
WORKDIR /app
COPY --from=build /app .
Expand Down
3 changes: 2 additions & 1 deletion Playground/Http2/Sni/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
// A real PEM pair for the DEFAULT certificate, or null to generate a self-signed localhost one.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideCert(ref certOverride, ref keyOverride);

Expand Down Expand Up @@ -116,7 +117,7 @@
var reactor = new Reactor(i, config);

// One TlsService per reactor. It owns the contexts - the default and one per name - and picks
// between them during each handshake on this ring. Each entry costs one OpenSSL context, built
// between them during each handshake on this ring. Each entry costs one BoringSSL context, built
// once at startup; choosing one per handshake is a lookup that allocates nothing, so serving
// ten names costs what serving one does.
reactor.OnStart = r => TlsService.Start(r, tlsOptions);
Expand Down
1 change: 1 addition & 0 deletions Playground/Http2/SslStream/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────

(string certPath, string keyPath) = QuicCert.Ensure(certOverride, keyOverride);
Expand Down
5 changes: 3 additions & 2 deletions Playground/Http2/Tls/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

// Hand OUTBOUND encryption to the kernel: the handler writes plaintext and the kernel makes the
// records. Off by default - OpenSSL both ways is the portable path, and on loopback the kernel
// records. Off by default - BoringSSL both ways is the portable path, and on loopback the kernel
// is not faster. Its real payoff is sendfile and NIC offload, which a benchmark here cannot see.
bool kernelTx = false;

Expand Down Expand Up @@ -109,7 +110,7 @@ .. Encoding.ASCII.GetBytes($"HTTP/1.1 200 OK\r\nContent-Length: {body.Length}\r\
KeyPath = keyPath, // PEM private key file
KeyPem = null, // in-memory PEM alternative to KeyPath
Alpn = ["h2", "http/1.1"], // ORDERED, most preferred first - a client offering both gets h2
KernelTx = kernelTx, // kTLS encrypt: kernel makes the records (off = OpenSSL both ways)
KernelTx = kernelTx, // kTLS encrypt: kernel makes the records (off = BoringSSL both ways)
KernelRx = kernelRx, // kTLS decrypt: needs KernelTx; experimental (see the knob above)
});

Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/ManagedBuffered/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/ManagedStreamedBoth/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/MutualTls/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
// The server's own certificate and key. Null generates a self-signed pair on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideCert(ref certOverride, ref keyOverride);

Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/Nghttp3Buffered/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────

(string certPath, string keyPath) = QuicCert.Ensure(certOverride, keyOverride);
Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/Nghttp3Request/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/Nghttp3Response/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/Nghttp3StreamedBoth/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

Env.OverrideCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions Playground/Http3/Sni/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
// A real PEM pair for the DEFAULT certificate, or null to generate a self-signed localhost one.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);

// The names this port answers for, beside the default. Add a line and it is served.
string[] hosts = ["alpha.test", "beta.test"];
Expand Down
4 changes: 2 additions & 2 deletions Playground/Proxy/H1ToH1/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// dotnet run -c Release --project Playground/Proxy/H1ToH1
// curl -k https://127.0.0.1:8443/
//
// Two TLS stacks are in play. INBOUND is ioxide's own termination, OpenSSL in both directions
// Two TLS stacks are in play. INBOUND is ioxide's own termination, BoringSSL in both directions
// by default. OUTBOUND is the client pool's: each upstream connection wraps its own
// TlsClientStream. Neither shows up in the proxying code.
//
Expand Down Expand Up @@ -104,7 +104,7 @@
KeyPath = keyPath, // PEM private key file (set exactly one of Path/Pem)
KeyPem = null, // in-memory PEM alternative to KeyPath
Alpn = ["http/1.1"], // protocols offered, most preferred first
KernelTx = false, // kTLS encrypt (off = OpenSSL both ways)
KernelTx = false, // kTLS encrypt (off = BoringSSL both ways)
KernelRx = false, // kTLS decrypt; requires KernelTx, experimental
});

Expand Down
2 changes: 1 addition & 1 deletion Playground/Proxy/H2ToH1/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
KeyPath = keyPath, // PEM private key file (set exactly one of Path/Pem)
KeyPem = null, // in-memory PEM alternative to KeyPath
Alpn = ["h2"], // protocols offered, most preferred first
KernelTx = false, // kTLS encrypt (off = OpenSSL both ways)
KernelTx = false, // kTLS encrypt (off = BoringSSL both ways)
KernelRx = false, // kTLS decrypt; requires KernelTx, experimental
});

Expand Down
1 change: 1 addition & 0 deletions Playground/Quic/Alpn/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────

(string certPath, string keyPath) = QuicCert.Ensure(certOverride, keyOverride);
Expand Down
1 change: 1 addition & 0 deletions Playground/Quic/Pipe/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────

(string certPath, string keyPath) = QuicCert.Ensure(certOverride, keyOverride);
Expand Down
1 change: 1 addition & 0 deletions Playground/Quic/Raw/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────

(string certPath, string keyPath) = QuicCert.Ensure(certOverride, keyOverride);
Expand Down
8 changes: 4 additions & 4 deletions Playground/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ delete it when you copy the file out and the literals are the whole configuratio
## Certificates

The samples that are about the certificate rather than about the transport. TLS exists twice in
ioxide and they are not one stack with two doors: on TCP, OpenSSL terminates **above** the
ioxide and they are not one stack with two doors: on TCP, BoringSSL terminates **above** the
transport and each reactor owns its own `TlsService`; on QUIC, TLS 1.3 lives **inside** the
transport (picotls, bundled with ngtcp2) and one `QuicEngine` is shared by every reactor. The same
feature therefore reads differently on each side, which is why each has its own sample.
Expand All @@ -53,13 +53,13 @@ trust.

## TLS over TCP

The certificate is terminated above the transport, by OpenSSL or by the kernel. All of these serve
The certificate is terminated above the transport, by BoringSSL or by the kernel. All of these serve
HTTP/1.1 framed by hand.

| Project | Lines | Shows | Packages |
| --- | --- | --- | --- |
| [`Tls.OpenSsl`](Tls/OpenSsl/Program.cs) | 243 | **The default**: OpenSSL both ways - no kernel module, and TLS 1.2 / any suite / resumption. | `ioxide` |
| [`Tls.KtlsTx`](Tls/KtlsTx/Program.cs) | 225 | The deployable kernel mode: kernel TX (the handler writes plaintext), OpenSSL RX. `Tls/Ktls` minus one line. `modprobe tls`. | `ioxide` |
| [`Tls.OpenSsl`](Tls/OpenSsl/Program.cs) | 243 | **The default**: BoringSSL both ways - no kernel module, and TLS 1.2 / any suite / resumption. | `ioxide` |
| [`Tls.KtlsTx`](Tls/KtlsTx/Program.cs) | 225 | The deployable kernel mode: kernel TX (the handler writes plaintext), BoringSSL RX. `Tls/Ktls` minus one line. `modprobe tls`. | `ioxide` |
| [`Tls.Ktls`](Tls/Ktls/Program.cs) | 225 | **Full kernel TLS** - both directions in the kernel. Experimental: a TLS 1.3 KeyUpdate cannot be read through `IORING_OP_RECV`. | `ioxide` |
| [`Tls.OpenSslPipes`](Tls/OpenSslPipes/Program.cs) | 186 | The default backend through an `IDuplexPipe`. | `ioxide` |
| [`Tls.KtlsPipes`](Tls/KtlsPipes/Program.cs) | 184 | Full kTLS through an `IDuplexPipe`. Its serve loop is byte-identical to `Tls/OpenSslPipes` - over a pipe the backend is invisible. | `ioxide` |
Expand Down
10 changes: 10 additions & 0 deletions Playground/Shared/Env.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ public static void OverrideQuic(ref ushort quicPort, ref int reactors)
reactors = Int("PLAYGROUND_REACTORS", reactors);
}

/// <summary>
/// PLAYGROUND_TLS_CERT and PLAYGROUND_TLS_KEY: a real PEM pair in place of the self-signed
/// localhost one a sample generates when its own literals are null.
/// </summary>
public static void OverrideTlsCert(ref string? certOverride, ref string? keyOverride)
{
certOverride = StrOrNull("PLAYGROUND_TLS_CERT") ?? certOverride;
keyOverride = StrOrNull("PLAYGROUND_TLS_KEY") ?? keyOverride;
}

/// <summary>
/// The kTLS knobs, same escape hatch. These exist so bench/tls-matrix.sh can measure one build
/// in every backend combination instead of rebuilding per cell - a sample that only ever runs
Expand Down
13 changes: 7 additions & 6 deletions Playground/Tls/Ktls/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
using Playground.Shared;

// ─────────────────────────────────────────────────────────────────────────────────────────────
// tls-ktls - FULL kernel TLS: the OpenSSL handshake runs over the ring, then both directions
// tls-ktls - FULL kernel TLS: the BoringSSL handshake runs over the ring, then both directions
// are handed to the kernel - the handler writes plaintext, the kernel makes the records, and
// recv delivers plaintext straight into ring memory. Experimental (see KernelRx below);
// Tls/KtlsTx is the deployable drop-back - kernel TX, OpenSSL RX - and Tls/OpenSsl the default.
// Tls/KtlsTx is the deployable drop-back - kernel TX, BoringSSL RX - and Tls/OpenSsl the default.
//
// sudo modprobe tls # needs the Linux 'tls' module + OpenSSL 3
// sudo modprobe tls # needs the Linux 'tls' module
// dotnet run -c Release --project Playground/Tls/Ktls
// curl -ks https://127.0.0.1:8443/ | head -c 40
//
Expand All @@ -33,6 +33,7 @@
// A real PEM pair, or null to generate a self-signed localhost cert on first run.
string? certOverride = null;
string? keyOverride = null;
Env.OverrideTlsCert(ref certOverride, ref keyOverride);
// ─────────────────────────────────────────────────────────────────────────────────────────────

(string certPath, string keyPath) = QuicCert.Ensure(certOverride, keyOverride);
Expand Down Expand Up @@ -68,14 +69,14 @@
KeyPath = keyPath, // PEM key file (or KeyPem for in-memory)
Alpn = ["http/1.1"], // protocols this port serves, most-preferred first

// NOT the default - TLS is OpenSSL both ways unless you ask for this. It is what lets the
// NOT the default - TLS is BoringSSL both ways unless you ask for this. It is what lets the
// handler below write PLAINTEXT to the connection: the kernel turns it into records on send.
// Remove this line and conn.Write would put cleartext on the wire.
KernelTx = true,

// Full kTLS: the kernel decrypts inbound too. Experimental - about one first connection in
// twelve fails outright, and a client sending a TLS 1.3 KeyUpdate loses the connection.
// Tls/KtlsTx is this same server without this line: kernel TX, OpenSSL RX, deployable today.
// Tls/KtlsTx is this same server without this line: kernel TX, BoringSSL RX, deployable today.
KernelRx = true,
};

Expand All @@ -97,7 +98,7 @@ .. Encoding.ASCII.GetBytes($"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCont
{
var reactor = new Reactor(i, config);

// One TlsService per reactor: it owns the OpenSSL contexts and drives handshakes on this ring.
// One TlsService per reactor: it owns the BoringSSL contexts and drives handshakes on this ring.
reactor.OnStart = r => TlsService.Start(r, tlsOptions);

reactor.TcpHandle = async (r, conn) =>
Expand Down
Loading
Loading