Skip to content

Commit 2e5f719

Browse files
committed
feat: complete rewrite to support hyper 1.0, quinn 0.11 and h3 0.0.8
1 parent 35d607b commit 2e5f719

953 files changed

Lines changed: 74909 additions & 222 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 423 additions & 165 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

h3client/Cargo.toml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ edition = "2021"
55

66
[dependencies]
77
tokio = { version = "1.38", features = ["rt-multi-thread", "macros", "net", "io-util"] }
8-
quinn = "0.10"
9-
h3 = "0.0.3"
10-
h3-quinn = "0.0.4"
11-
http = "0.2"
8+
quinn = "0.11"
9+
h3 = "0.0.8"
10+
h3-quinn = "0.0.10"
11+
http = "1.0"
12+
http-body = "1.0"
13+
http-body-util = "0.1"
1214
bytes = "1.4"
1315
anyhow = "1.0"
1416
tracing = "0.1"
1517
tracing-subscriber = { version = "0.3", features = ["fmt"] }
16-
rustls = "0.21"
17-
rustls-pemfile = "1.0"
18+
rustls = { version = "0.23", features = ["ring"] }
19+
rustls-pki-types = "1.0"
20+
rustls-pemfile = "2.0"

h3client/src/main.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{fs, net::SocketAddr, sync::Arc};
44
use http::Request;
55
use h3_quinn::Connection;
66
use bytes::Buf;
7+
use rustls::pki_types::CertificateDer;
78

89
#[tokio::main]
910
async fn main() -> Result<()> {
@@ -15,17 +16,18 @@ async fn main() -> Result<()> {
1516
// trust self-signed cert
1617
let cert = fs::read("cert.pem")?;
1718
let mut roots = rustls::RootCertStore::empty();
18-
let certs = rustls_pemfile::certs(&mut &*cert)?;
19+
let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut &*cert).collect::<Result<_, _>>()?;
1920
for c in certs {
20-
roots.add(&rustls::Certificate(c))?;
21+
roots.add(c)?;
2122
}
22-
let mut crypto = rustls::ClientConfig::builder()
23-
.with_safe_defaults()
23+
24+
let mut crypto = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
25+
.with_safe_default_protocol_versions()?
2426
.with_root_certificates(roots)
2527
.with_no_client_auth();
2628
crypto.alpn_protocols = vec![b"h3".to_vec(), b"h3-29".to_vec()];
2729

28-
let client_config = quinn::ClientConfig::new(Arc::new(crypto));
30+
let client_config = quinn::ClientConfig::new(Arc::new(quinn::crypto::rustls::QuicClientConfig::try_from(crypto)?));
2931
endpoint.set_default_client_config(client_config);
3032

3133
let quinn_conn = endpoint.connect(server_addr, "localhost")?.await?;
@@ -34,9 +36,7 @@ async fn main() -> Result<()> {
3436
let h3_conn_wrapped = Connection::new(quinn_conn);
3537
let (mut driver, mut send_request) = h3::client::new(h3_conn_wrapped).await?;
3638
tokio::spawn(async move {
37-
if let Err(e) = std::future::poll_fn(|cx| driver.poll_close(cx)).await {
38-
eprintln!("driver err: {:?}", e);
39-
}
39+
let _ = std::future::poll_fn(|cx| driver.poll_close(cx)).await;
4040
});
4141

4242
// Example: send CONNECT to create tunnel to example.com:443

h3proxy-cli/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ anyhow = "1.0"
1010
tracing = "0.1"
1111
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
1212
clap = { version = "4.4", features = ["derive"] }
13-
quinn = "0.10"
14-
rustls = "0.21"
15-
rustls-pemfile = "1.0"
13+
quinn = "0.11"
14+
rustls = { version = "0.23", features = ["ring"] }
15+
rustls-pki-types = "1.0"
16+
rustls-pemfile = "2.0"

h3proxy-cli/src/main.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ async fn main() -> Result<()> {
2626
let cert_data = fs::read(&args.cert).with_context(|| format!("failed to read cert file: {}", args.cert))?;
2727
let key_data = fs::read(&args.key).with_context(|| format!("failed to read key file: {}", args.key))?;
2828

29-
let certs = rustls_pemfile::certs(&mut &*cert_data)
30-
.context("failed to parse certs")?
29+
let certs: Vec<_> = rustls_pemfile::certs(&mut &*cert_data)
30+
.collect::<Result<_, _>>()
31+
.context("failed to parse certs")?;
32+
33+
let mut keys: Vec<_> = rustls_pemfile::private_key(&mut &*key_data)
34+
.context("failed to parse key")?
3135
.into_iter()
32-
.map(rustls::Certificate)
3336
.collect();
34-
35-
let mut keys = rustls_pemfile::rsa_private_keys(&mut &*key_data)
36-
.context("failed to parse key")?;
37-
let priv_key = rustls::PrivateKey(keys.remove(0));
37+
let priv_key = keys.remove(0);
3838

3939
let config = ProxyConfig {
4040
listen_addr: args.listen,

h3proxy-lib/Cargo.toml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ edition = "2021"
55

66
[dependencies]
77
tokio = { version = "1.38", features = ["rt-multi-thread", "macros", "net", "io-util"] }
8-
quinn = "0.10"
9-
h3 = "0.0.3"
10-
h3-quinn = "0.0.4"
8+
quinn = "0.11"
9+
h3 = "0.0.8"
10+
h3-quinn = "0.0.10"
1111
bytes = "1.4"
12-
http = "0.2"
13-
hyper = { version = "0.14", features = ["client", "http1", "stream", "tcp"] }
12+
http = "1.0"
13+
http-body = "1.0"
14+
http-body-util = "0.1"
15+
tokio-stream = "0.1"
16+
rustls-pki-types = "1.0"
17+
hyper = { version = "1.0", features = ["http1", "client"] }
18+
hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"] }
1419
anyhow = "1.0"
1520
tracing = "0.1"
1621
futures = "0.3"
17-
rustls = "0.21"
22+
rustls = { version = "0.23", features = ["ring"] }

h3proxy-lib/src/config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use rustls::{Certificate, PrivateKey};
1+
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
22
use std::net::SocketAddr;
33

44
pub struct ProxyConfig {
55
pub listen_addr: SocketAddr,
6-
pub cert_chain: Vec<Certificate>,
7-
pub priv_key: PrivateKey,
6+
pub cert_chain: Vec<CertificateDer<'static>>,
7+
pub priv_key: PrivateKeyDer<'static>,
88
}

h3proxy-lib/src/handler.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ use bytes::{Buf, Bytes};
33
use h3_quinn::BidiStream;
44
use h3::server::RequestStream;
55
use http::{Request, Response};
6-
use hyper::body::HttpBody;
6+
use http_body_util::{BodyExt, StreamBody};
77
use tokio::io::{AsyncReadExt, AsyncWriteExt};
88
use tokio::net::TcpStream;
99
use tracing::{error, info};
10+
use hyper_util::client::legacy::Client;
11+
use hyper_util::client::legacy::connect::HttpConnector;
1012

1113
pub async fn handle_request(
1214
req: Request<()>,
1315
mut stream: RequestStream<BidiStream<Bytes>, Bytes>,
14-
hyper_client: hyper::Client<hyper::client::HttpConnector>,
16+
hyper_client: Client<HttpConnector, StreamBody<tokio_stream::wrappers::ReceiverStream<Result<hyper::body::Frame<Bytes>, anyhow::Error>>>>,
1517
) -> Result<()> {
1618
let method = req.method().clone();
1719
let uri = req.uri().clone();
@@ -109,22 +111,23 @@ pub async fn handle_request(
109111
builder = builder.header(name.as_str(), value.as_bytes());
110112
}
111113

112-
let (mut sender, body) = hyper::Body::channel();
113-
let hyper_req = builder.body(body)?;
114+
let (tx_body, rx_body) = tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, anyhow::Error>>(10);
115+
let stream_body = StreamBody::new(tokio_stream::wrappers::ReceiverStream::new(rx_body));
116+
let hyper_req = builder.body(stream_body)?;
114117

115118
let (mut tx, mut rx) = stream.split();
116119

117120
tokio::spawn(async move {
118121
while let Ok(Some(mut chunk)) = rx.recv_data().await {
119122
let bytes = chunk.copy_to_bytes(chunk.remaining());
120-
if let Err(e) = sender.send_data(bytes).await {
121-
error!("failed to stream body to upstream: {:?}", e);
123+
if let Err(_) = tx_body.send(Ok(hyper::body::Frame::data(bytes))).await {
124+
error!("failed to stream body to upstream");
122125
break;
123126
}
124127
}
125128
});
126129

127-
let resp = hyper_client.request(hyper_req).await?;
130+
let mut resp = hyper_client.request(hyper_req).await?;
128131

129132
let mut resp_builder = http::response::Builder::new();
130133
resp_builder = resp_builder.status(resp.status().as_u16());
@@ -134,10 +137,12 @@ pub async fn handle_request(
134137

135138
tx.send_response(resp_builder.body(())?).await?;
136139

137-
let mut body_stream = resp.into_body();
138-
while let Some(chunk_res) = body_stream.data().await {
139-
let chunk = chunk_res?;
140-
tx.send_data(chunk).await?;
140+
while let Some(frame_res) = resp.body_mut().frame().await {
141+
if let Ok(frame) = frame_res {
142+
if let Some(chunk) = frame.data_ref() {
143+
tx.send_data(chunk.clone()).await?;
144+
}
145+
}
141146
}
142147
tx.finish().await?;
143148

h3proxy-lib/src/server.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use h3_quinn::Connection;
33
use quinn::Endpoint;
44
use std::sync::Arc;
55
use tracing::{error, info};
6+
use hyper_util::client::legacy::Client;
7+
use hyper_util::rt::TokioExecutor;
68

79
use crate::config::ProxyConfig;
810
use crate::handler::handle_request;
@@ -17,20 +19,20 @@ impl ProxyServer {
1719
}
1820

1921
pub async fn serve(self) -> Result<()> {
20-
let mut server_crypto = rustls::ServerConfig::builder()
21-
.with_safe_defaults()
22+
let mut server_crypto = rustls::ServerConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
23+
.with_safe_default_protocol_versions()?
2224
.with_no_client_auth()
2325
.with_single_cert(self.config.cert_chain, self.config.priv_key)?;
2426
server_crypto.alpn_protocols = vec![b"h3".to_vec(), b"h3-29".to_vec()];
2527

26-
let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(server_crypto));
28+
let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto)?));
2729
server_config.transport_config(Arc::new(quinn::TransportConfig::default()));
2830

2931
let addr = self.config.listen_addr;
3032
let endpoint = Endpoint::server(server_config, addr)?;
3133
info!(%addr, "h3 proxy listening");
3234

33-
let hyper_client = hyper::Client::builder().build_http();
35+
let hyper_client = Client::builder(TokioExecutor::new()).build_http();
3436

3537
while let Some(connecting) = endpoint.accept().await {
3638
let quinn_conn = match connecting.await {
@@ -56,14 +58,16 @@ impl ProxyServer {
5658
}
5759
};
5860

59-
while let Some(accept) = h3_server.accept().await.unwrap_or(None) {
60-
let (req, stream) = accept;
61-
let hyper_client = hyper_client.clone();
62-
tokio::spawn(async move {
63-
if let Err(e) = handle_request(req, stream, hyper_client).await {
64-
error!("request err: {:?}", e);
65-
}
66-
});
61+
while let Ok(Some(req_resolver)) = h3_server.accept().await {
62+
if let Ok(resolved) = req_resolver.resolve_request().await {
63+
let (req, stream) = resolved;
64+
let hyper_client = hyper_client.clone();
65+
tokio::spawn(async move {
66+
if let Err(e) = handle_request(req, stream, hyper_client).await {
67+
error!("request err: {:?}", e);
68+
}
69+
});
70+
}
6771
}
6872
info!("connection closed");
6973
});

target/.rustc_info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"rustc_fingerprint":17866707384158916766,"outputs":{"10803282065846393305":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\iHsin\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"12855261153397992890":{"success":true,"status":"","code":0,"stdout":"rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: x86_64-pc-windows-msvc\nrelease: 1.93.1\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}}
1+
{"rustc_fingerprint":17866707384158916766,"outputs":{"12855261153397992890":{"success":true,"status":"","code":0,"stdout":"rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: x86_64-pc-windows-msvc\nrelease: 1.93.1\nLLVM version: 21.1.8\n","stderr":""},"10803282065846393305":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\iHsin\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}

0 commit comments

Comments
 (0)