Skip to content

Commit e05406d

Browse files
authored
feat(tls): Treat different TLS configs as distinct sessions (#779)
1 parent 7364de2 commit e05406d

30 files changed

Lines changed: 798 additions & 571 deletions

Cargo.toml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ socket2 = { version = "0.5.10", features = ["all"] }
8989
percent-encoding = "2.3"
9090
ipnet = "2.11.0"
9191
schnellru = { version = "0.2.4", default-features = false }
92+
ahash = { version = "0.8.11", default-features = false }
9293

9394
## boring-tls
9495
boring2 = { version = "5.0.0-alpha.2", features = ["pq-experimental"] }
9596
tokio-boring2 = { version = "5.0.0-alpha.2", features = ["pq-experimental"] }
9697
brotli = "8.0.1"
9798
flate2 = "1.1.2"
9899
zstd = "0.13.3"
99-
linked_hash_set = "0.1"
100100

101101
# Optional deps...
102102

@@ -151,30 +151,23 @@ libc = "0.2.173"
151151
hyper = { version = "1.1.0", default-features = false, features = [
152152
"http1",
153153
"http2",
154-
"client",
155154
"server",
156155
] }
157156
hyper-util = { version = "0.1.13", features = [
158157
"http1",
159158
"http2",
160-
"client",
161-
"client-legacy",
162159
"server-auto",
163160
"server-graceful",
164161
"tokio",
165162
] }
166163
serde = { version = "1.0", features = ["derive"] }
167-
flate2 = "1.1.1"
168-
zstd = "0.13"
169-
brotli = "8.0.0"
170164
tokio = { version = "1.0", default-features = false, features = [
171165
"macros",
172166
"rt-multi-thread",
173167
] }
174168
futures = { version = "0.3.0", default-features = false, features = ["std"] }
175169
tower = { version = "0.5.2", default-features = false, features = ["limit"] }
176170
libc = "0.2"
177-
178171
env_logger = "0.11.8"
179172
tracing = "0.1"
180173
tracing-subscriber = "0.3.19"

src/client/client/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use crate::{
5454
IntoUrl, Method, OriginalHeaders, Proxy,
5555
connect::{BoxedConnectorLayer, BoxedConnectorService, Conn, Connector, Unnameable},
5656
core::{
57-
client::{Builder, Client as HyperClient, connect::TcpConnectOptions},
57+
client::{Builder, Client as NativeClient, connect::TcpConnectOptions},
5858
ext::RequestConfig,
5959
rt::{TokioExecutor, tokio::TokioTimer},
6060
},
@@ -192,7 +192,7 @@ impl ClientBuilder {
192192
pool_idle_timeout: Some(Duration::from_secs(90)),
193193
pool_max_idle_per_host: usize::MAX,
194194
pool_max_size: None,
195-
// TODO: Re-enable default duration once hyper's HttpConnector is fixed
195+
// TODO: Re-enable default duration once core's HttpConnector is fixed
196196
// to no longer error when an option fails.
197197
tcp_keepalive: None,
198198
tcp_keepalive_interval: None,
@@ -215,7 +215,7 @@ impl ClientBuilder {
215215
dns_overrides: HashMap::new(),
216216
dns_resolver: None,
217217
http_version_pref: HttpVersionPref::All,
218-
builder: HyperClient::builder(TokioExecutor::new()),
218+
builder: NativeClient::builder(TokioExecutor::new()),
219219
https_only: false,
220220
http1_config: Http1Config::default(),
221221
http2_config: Http2Config::default(),

src/client/request.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::{
3131
core::{
3232
client::{config::TransportConfig, connect::TcpConnectOptions},
3333
ext::{
34-
RequestConfig, RequestHttpVersionPref, RequestOriginalHeaders, RequestProxyMatcher,
34+
RequestConfig, RequestEnforcedHttpVersion, RequestOriginalHeaders, RequestProxyMatcher,
3535
RequestTcpConnectOptions, RequestTransportConfig,
3636
},
3737
},
@@ -134,13 +134,13 @@ impl Request {
134134
/// Get the http version.
135135
#[inline]
136136
pub fn version(&self) -> Option<&Version> {
137-
RequestConfig::<RequestHttpVersionPref>::get(&self.extensions)
137+
RequestConfig::<RequestEnforcedHttpVersion>::get(&self.extensions)
138138
}
139139

140140
/// Get a mutable reference to the http version.
141141
#[inline(always)]
142142
pub fn version_mut(&mut self) -> &mut Option<Version> {
143-
RequestConfig::<RequestHttpVersionPref>::get_mut(&mut self.extensions)
143+
RequestConfig::<RequestEnforcedHttpVersion>::get_mut(&mut self.extensions)
144144
}
145145

146146
/// Get a mutable reference to the timeout.

src/connect.rs

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use crate::{
3232
error::{BoxError, TimedOut, map_timeout_to_connector_error},
3333
proxy::{Intercepted, Matcher as ProxyMatcher},
3434
tls::{
35-
CertStore, HttpsConnector, Identity, KeyLogPolicy, MaybeHttpsStream, TlsConfig,
36-
TlsConnector, TlsConnectorBuilder, TlsInfo, TlsVersion,
35+
CertStore, EstablishedConn, HttpsConnector, Identity, KeyLogPolicy, MaybeHttpsStream,
36+
TlsConfig, TlsConnector, TlsConnectorBuilder, TlsInfo, TlsVersion,
3737
},
3838
};
3939

@@ -305,7 +305,6 @@ impl Connector {
305305
#[cfg(feature = "socks")]
306306
resolver: resolver.clone(),
307307
http: {
308-
// Create a new HttpConnector with the provided resolver
309308
let mut http = HttpConnector::new_with_resolver(resolver);
310309
http.enforce_http(false);
311310
http
@@ -314,8 +313,6 @@ impl Connector {
314313
verbose: verbose::OFF,
315314
timeout: None,
316315
tcp_nodelay: false,
317-
318-
// TLS connector and its configuration
319316
tls_info: false,
320317
tls_builder: TlsConnector::builder(),
321318
}
@@ -367,7 +364,25 @@ pub(crate) struct ConnectorService {
367364
}
368365

369366
impl ConnectorService {
370-
async fn connect(self, mut req: ConnRequest, is_proxy: bool) -> Result<Conn, BoxError> {
367+
/// Constructs an HTTPS connector by wrapping an `HttpConnector`
368+
/// with the appropriate TLS configuration.
369+
fn build_tls_connector(
370+
&self,
371+
mut http: HttpConnector,
372+
req: &mut ConnRequest,
373+
) -> Result<HttpsConnector<HttpConnector>, BoxError> {
374+
let ex_data = req.ex_data();
375+
http.set_tcp_connect_options(ex_data.tcp_connect_options().cloned());
376+
let tls = match ex_data.tls_config() {
377+
Some(cfg) => self.tls_builder.build(cfg.clone())?,
378+
None => self.tls.clone(),
379+
};
380+
Ok(HttpsConnector::with_connector(http, tls))
381+
}
382+
383+
/// Establishes a direct connection to the target URI without using a proxy.
384+
/// May perform a plain TCP or a TLS handshake depending on the URI scheme.
385+
async fn connect_direct(self, mut req: ConnRequest, is_proxy: bool) -> Result<Conn, BoxError> {
371386
trace!("connect with maybe proxy: {:?}", is_proxy);
372387

373388
let uri = req.uri().clone();
@@ -380,8 +395,8 @@ impl ConnectorService {
380395
http.set_nodelay(true);
381396
}
382397

383-
let mut connector = self.create_https_connector(http, &mut req)?;
384-
let io = connector.call(uri).await?;
398+
let mut connector = self.build_tls_connector(http, &mut req)?;
399+
let io = connector.call(req).await?;
385400

386401
// If the connection is HTTPS, wrap the TLS stream in a TlsConn for unified handling.
387402
// For plain HTTP, use the stream directly without additional wrapping.
@@ -403,7 +418,9 @@ impl ConnectorService {
403418
})
404419
}
405420

406-
async fn connect_via_proxy(
421+
/// Establishes a connection through a specified proxy.
422+
/// Supports both SOCKS and HTTP tunneling proxies.
423+
async fn connect_with_proxy(
407424
self,
408425
mut req: ConnRequest,
409426
proxy: Intercepted,
@@ -437,8 +454,9 @@ impl ConnectorService {
437454

438455
return if uri.scheme() == Some(&Scheme::HTTPS) {
439456
trace!("socks HTTPS over proxy");
440-
let mut connector = self.create_https_connector(self.http.clone(), &mut req)?;
441-
let io = connector.call((uri, conn)).await?;
457+
let mut connector = self.build_tls_connector(self.http.clone(), &mut req)?;
458+
let established_conn = EstablishedConn::new(req, conn);
459+
let io = connector.call(established_conn).await?;
442460

443461
Ok(Conn {
444462
inner: self.verbose.wrap(TlsConn {
@@ -460,7 +478,7 @@ impl ConnectorService {
460478
// Handle HTTPS proxy tunneling connection
461479
if uri.scheme() == Some(&Scheme::HTTPS) {
462480
trace!("tunneling HTTPS over HTTP proxy: {:?}", proxy_uri);
463-
let mut connector = self.create_https_connector(self.http.clone(), &mut req)?;
481+
let mut connector = self.build_tls_connector(self.http.clone(), &mut req)?;
464482

465483
let mut tunnel = proxy::Tunnel::new(proxy_uri, connector.clone());
466484
if let Some(auth) = proxy.basic_auth() {
@@ -473,10 +491,11 @@ impl ConnectorService {
473491

474492
// We don't wrap this again in an HttpsConnector since that uses Maybe,
475493
// and we know this is definitely HTTPS.
476-
let tunneled = tunnel.call(uri.clone()).await?;
494+
let tunneled = tunnel.call(uri).await?;
477495
let tunneled = TokioIo::new(tunneled);
478496
let tunneled = TokioIo::new(tunneled);
479-
let io = connector.call((uri, tunneled)).await?;
497+
let established_conn = EstablishedConn::new(req, tunneled);
498+
let io = connector.call(established_conn).await?;
480499

481500
return Ok(Conn {
482501
inner: self.verbose.wrap(TlsConn {
@@ -487,44 +506,42 @@ impl ConnectorService {
487506
});
488507
}
489508

490-
// Update the connect URI to the proxy URI
491509
*req.uri_mut() = proxy_uri;
492-
493-
self.connect(req, true).await
510+
self.connect_direct(req, true).await
494511
}
495512

496-
fn create_https_connector(
497-
&self,
498-
http: HttpConnector,
499-
conn_req: &mut ConnRequest,
500-
) -> Result<HttpsConnector<HttpConnector>, BoxError> {
501-
let (tcp_opts, tls_cfg, alpn_protocol) = conn_req.take_config_bundle();
502-
503-
let tls = tls_cfg
504-
.map(|cfg| self.tls_builder.build(cfg))
505-
.transpose()?
506-
.unwrap_or_else(|| self.tls.clone());
513+
/// Automatically selects between a direct or proxied connection
514+
/// based on the request and configured proxy matchers.
515+
/// Applies a timeout if configured.
516+
async fn connect_auto(self, req: ConnRequest) -> Result<Conn, BoxError> {
517+
debug!("starting new connection: {:?}", req.uri());
507518

508-
let mut connector = HttpsConnector::with_connector(http, tls);
509-
connector.set_alpn_protocol(alpn_protocol);
510-
connector.set_tcp_connect_options(tcp_opts);
519+
let intercepted = req
520+
.ex_data()
521+
.proxy_matcher()
522+
.and_then(|scheme| scheme.intercept(req.uri()))
523+
.or_else(|| {
524+
self.proxies
525+
.iter()
526+
.find_map(|prox| prox.intercept(req.uri()))
527+
});
511528

512-
Ok(connector)
513-
}
514-
}
529+
let timeout = self.timeout;
530+
let fut = async {
531+
if let Some(intercepted) = intercepted {
532+
self.connect_with_proxy(req, intercepted).await
533+
} else {
534+
self.connect_direct(req, false).await
535+
}
536+
};
515537

516-
async fn with_timeout<T, F>(f: F, timeout: Option<Duration>) -> Result<T, BoxError>
517-
where
518-
F: Future<Output = Result<T, BoxError>>,
519-
{
520-
if let Some(to) = timeout {
521-
match tokio::time::timeout(to, f).await {
522-
Err(_elapsed) => Err(Box::new(TimedOut) as BoxError),
523-
Ok(Ok(try_res)) => Ok(try_res),
524-
Ok(Err(e)) => Err(e),
538+
if let Some(to) = timeout {
539+
tokio::time::timeout(to, fut)
540+
.await
541+
.map_err(|_| BoxError::from(TimedOut))?
542+
} else {
543+
fut.await
525544
}
526-
} else {
527-
f.await
528545
}
529546
}
530547

@@ -538,26 +555,9 @@ impl Service<ConnRequest> for ConnectorService {
538555
Poll::Ready(Ok(()))
539556
}
540557

541-
fn call(&mut self, mut req: ConnRequest) -> Self::Future {
542-
debug!("starting new connection: {:?}", req.uri());
543-
544-
let intercepted = req
545-
.take_proxy_matcher()
546-
.and_then(|scheme| scheme.intercept(req.uri()))
547-
.or_else(|| {
548-
self.proxies
549-
.iter()
550-
.find_map(|prox| prox.intercept(req.uri()))
551-
});
552-
553-
if let Some(intercepted) = intercepted {
554-
return Box::pin(with_timeout(
555-
self.clone().connect_via_proxy(req, intercepted),
556-
self.timeout,
557-
));
558-
}
559-
560-
Box::pin(with_timeout(self.clone().connect(req, false), self.timeout))
558+
#[inline(always)]
559+
fn call(&mut self, req: ConnRequest) -> Self::Future {
560+
Box::pin(self.clone().connect_auto(req))
561561
}
562562
}
563563

@@ -627,7 +627,7 @@ mod conn {
627627

628628
pin_project! {
629629
/// Note: the `is_proxy` member means *is plain text HTTP proxy*.
630-
/// This tells hyper whether the URI should be written in
630+
/// This tells core whether the URI should be written in
631631
/// * origin-form (`GET /just/a/path HTTP/1.1`), when `is_proxy == false`, or
632632
/// * absolute-form (`GET http://foo.bar/and/a/path HTTP/1.1`), otherwise.
633633
pub struct Conn {

src/core/client/connect/http.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -606,11 +606,11 @@ impl<'a> ConnectingTcp<'a> {
606606
config
607607
.tcp_connect_options
608608
.as_ref()
609-
.and_then(|opt| opt.local_address_ipv4),
609+
.and_then(|opt| opt.local_ipv4),
610610
config
611611
.tcp_connect_options
612612
.as_ref()
613-
.and_then(|opt| opt.local_address_ipv6),
613+
.and_then(|opt| opt.local_ipv6),
614614
);
615615
if fallback_addrs.is_empty() {
616616
return ConnectingTcp {
@@ -813,11 +813,11 @@ fn connect(
813813
&config
814814
.tcp_connect_options
815815
.as_ref()
816-
.and_then(|opt| opt.local_address_ipv4),
816+
.and_then(|opt| opt.local_ipv4),
817817
&config
818818
.tcp_connect_options
819819
.as_ref()
820-
.and_then(|opt| opt.local_address_ipv6),
820+
.and_then(|opt| opt.local_ipv6),
821821
)
822822
.map_err(ConnectError::m("tcp bind local error"))?;
823823

0 commit comments

Comments
 (0)