Skip to content

Commit 2af1b16

Browse files
authored
feat(s2n-quic-core) TLS offloading (#2721)
1 parent 422d2c3 commit 2af1b16

File tree

13 files changed

+1222
-2
lines changed

13 files changed

+1222
-2
lines changed

examples/tls-offloading/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "tls-offloading"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
s2n-quic = { version = "1", path = "../../quic/s2n-quic", features = ["unstable-offload-tls"]}
8+
tokio = { version = "1", features = ["full"] }

examples/tls-offloading/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# TLS offload
2+
3+
s2n-quic is single-threaded by default. This can cause performance issues in the instance where many clients try to connect to a single s2n-quic server at the same time. Each incoming Client Hello will cause the s2n-quic server event loop to be blocked while the TLS provider completes the expensive cryptographic operations necessary to process the Client Hello. This has the potential to slow down all existing connections in favor of new ones. The TLS offloading feature attempts to alleviate this problem by moving each TLS connection to a separate async task, which can then be spawned by the runtime the user provides.
4+
5+
To do this, implement the `offload::Executor` trait with the runtime of your choice. In this example, we use the `tokio::spawn` function as our executor:
6+
```
7+
struct TokioExecutor;
8+
impl Executor for TokioExecutor {
9+
fn spawn(&self, task: impl core::future::Future<Output = ()> + Send + 'static) {
10+
tokio::spawn(task);
11+
}
12+
}
13+
```
14+
15+
# Warning
16+
The default offloading feature as-is may result in packet loss in the handshake, depending on how packets arrive to the offload endpoint. This packet loss will slow down the handshake, as QUIC has to detect the loss and the peer has to resend the lost packets. We have an upcoming feature that will combat this packet loss and will probably be required to achieve the fastest handshakes possible with s2n-quic: https://github.com/aws/s2n-quic/pull/2668. However, it is still in the PR process.
17+
18+
# Set-up
19+
20+
Currently offloading is disabled by default as it is still in development. It can be enabled by adding this line to your Cargo.toml file:
21+
22+
```toml
23+
[dependencies]
24+
s2n-quic = { version = "1", features = ["unstable-offload-tls"]}
25+
```
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use s2n_quic::{
5+
Client,
6+
client::Connect,
7+
provider::tls::{
8+
default,
9+
offload::{Executor, OffloadBuilder},
10+
},
11+
};
12+
use std::{error::Error, net::SocketAddr};
13+
14+
/// NOTE: this certificate is to be used for demonstration purposes only!
15+
pub static CERT_PEM: &str = include_str!(concat!(
16+
env!("CARGO_MANIFEST_DIR"),
17+
"/../../quic/s2n-quic-core/certs/cert.pem"
18+
));
19+
20+
struct TokioExecutor;
21+
impl Executor for TokioExecutor {
22+
fn spawn(&self, task: impl core::future::Future<Output = ()> + Send + 'static) {
23+
tokio::spawn(task);
24+
}
25+
}
26+
27+
#[tokio::main]
28+
async fn main() -> Result<(), Box<dyn Error>> {
29+
let tls = default::Client::builder()
30+
.with_certificate(CERT_PEM)?
31+
.build()?;
32+
let tls_endpoint = OffloadBuilder::new()
33+
.with_endpoint(tls)
34+
.with_executor(TokioExecutor)
35+
.build();
36+
37+
let client = Client::builder()
38+
.with_tls(tls_endpoint)?
39+
.with_io("0.0.0.0:0")?
40+
.start()?;
41+
42+
let addr: SocketAddr = "127.0.0.1:4433".parse()?;
43+
let connect = Connect::new(addr).with_server_name("localhost");
44+
let mut connection = client.connect(connect).await?;
45+
46+
// ensure the connection doesn't time out with inactivity
47+
connection.keep_alive(true)?;
48+
49+
// open a new stream and split the receiving and sending sides
50+
let stream = connection.open_bidirectional_stream().await?;
51+
let (mut receive_stream, mut send_stream) = stream.split();
52+
53+
// spawn a task that copies responses from the server to stdout
54+
tokio::spawn(async move {
55+
let mut stdout = tokio::io::stdout();
56+
let _ = tokio::io::copy(&mut receive_stream, &mut stdout).await;
57+
});
58+
59+
// copy data from stdin and send it to the server
60+
let mut stdin = tokio::io::stdin();
61+
tokio::io::copy(&mut stdin, &mut send_stream).await?;
62+
63+
Ok(())
64+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use s2n_quic::{
5+
Server,
6+
provider::tls::{
7+
default,
8+
offload::{Executor, OffloadBuilder},
9+
},
10+
};
11+
use std::error::Error;
12+
13+
/// NOTE: this certificate is to be used for demonstration purposes only!
14+
pub static CERT_PEM: &str = include_str!(concat!(
15+
env!("CARGO_MANIFEST_DIR"),
16+
"/../../quic/s2n-quic-core/certs/cert.pem"
17+
));
18+
/// NOTE: this certificate is to be used for demonstration purposes only!
19+
pub static KEY_PEM: &str = include_str!(concat!(
20+
env!("CARGO_MANIFEST_DIR"),
21+
"/../../quic/s2n-quic-core/certs/key.pem"
22+
));
23+
24+
struct TokioExecutor;
25+
impl Executor for TokioExecutor {
26+
fn spawn(&self, task: impl core::future::Future<Output = ()> + Send + 'static) {
27+
tokio::spawn(task);
28+
}
29+
}
30+
31+
#[tokio::main]
32+
async fn main() -> Result<(), Box<dyn Error>> {
33+
let tls = default::Server::builder()
34+
.with_certificate(CERT_PEM, KEY_PEM)?
35+
.build()?;
36+
37+
let tls_endpoint = OffloadBuilder::new()
38+
.with_endpoint(tls)
39+
.with_executor(TokioExecutor)
40+
.build();
41+
42+
let mut server = Server::builder()
43+
.with_tls(tls_endpoint)?
44+
.with_io("127.0.0.1:4433")?
45+
.start()?;
46+
47+
while let Some(mut connection) = server.accept().await {
48+
// spawn a new task for the connection
49+
tokio::spawn(async move {
50+
eprintln!("Connection accepted from {:?}", connection.remote_addr());
51+
52+
while let Ok(Some(mut stream)) = connection.accept_bidirectional_stream().await {
53+
// spawn a new task for the stream
54+
tokio::spawn(async move {
55+
eprintln!("Stream opened from {:?}", stream.connection().remote_addr());
56+
57+
// echo any data back to the stream
58+
while let Ok(Some(data)) = stream.receive().await {
59+
stream.send(data).await.expect("stream should be open");
60+
}
61+
});
62+
}
63+
});
64+
}
65+
66+
Ok(())
67+
}

quic/s2n-quic-core/src/crypto/tls.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub mod null;
2020
#[cfg(feature = "alloc")]
2121
pub mod slow_tls;
2222

23+
#[cfg(feature = "std")]
24+
pub mod offload;
25+
2326
/// Holds all application parameters which are exchanged within the TLS handshake.
2427
#[derive(Debug)]
2528
pub struct ApplicationParameters<'a> {

0 commit comments

Comments
 (0)