use std::{
borrow::Cow,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use futures_util::future::BoxFuture;
use tokio_btls::SslStream;
use tower::{
BoxError, Layer, Service, ServiceBuilder, ServiceExt,
util::{BoxCloneSyncService, Either, MapRequest, MapRequestLayer},
};
#[cfg(unix)]
use super::net::UnixConnector;
use super::{
AsyncConnWithInfo, BoxedConnectorLayer, BoxedTransportConnector, Conn, Connection,
HttpConnector, TlsConn, TlsInfoFactory, Unnameable,
descriptor::ConnectionDescriptor,
http::HttpConnect,
net::TcpConnector,
proxy,
timeout::{Timeout, TimeoutLayer},
verbose::Verbose,
};
use crate::{
dns::DynResolver,
error::{ProxyConnect, map_timeout_to_connector_error},
ext::UriExt,
proxy::{Intercepted, Matcher as ProxyMatcher, matcher::Intercept},
rt::Timer,
tls::{
TlsOptions,
conn::{
EstablishedConn, HttpsConnector, MaybeHttpsStream, TlsConnector, TlsConnectorBuilder,
},
},
};
#[derive(Clone)]
struct Config {
proxies: Arc<Vec<ProxyMatcher>>,
verbose: Verbose,
nodelay: bool,
tls_info: bool,
}
pub struct ConnectorBuilder {
config: Config,
timer: Timer,
timeout: Option<Duration>,
#[cfg(feature = "socks")]
resolver: DynResolver,
http: HttpConnector,
builder: TlsConnectorBuilder,
}
pub type Connector = Either<
Timeout<TransportConnector>,
MapRequest<BoxedTransportConnector, fn(ConnectionDescriptor) -> Unnameable>,
>;
#[derive(Clone)]
pub struct TransportConnector {
config: Config,
#[cfg(feature = "socks")]
resolver: DynResolver,
tls: TlsConnector,
http: HttpConnector,
builder: Arc<TlsConnectorBuilder>,
}
impl ConnectorBuilder {
pub(crate) fn new(proxies: Vec<ProxyMatcher>, resolver: DynResolver) -> Self {
Self {
config: Config {
proxies: Arc::new(proxies),
verbose: Verbose::OFF,
nodelay: true,
tls_info: false,
},
timer: Timer::default(),
timeout: None,
#[cfg(feature = "socks")]
resolver: resolver.clone(),
http: HttpConnector::new(resolver, TcpConnector::new()),
builder: TlsConnector::builder(),
}
}
#[inline]
pub fn with_http<F>(mut self, call: F) -> ConnectorBuilder
where
F: FnOnce(&mut HttpConnector),
{
call(&mut self.http);
self
}
#[inline]
pub fn with_tls<F>(mut self, call: F) -> ConnectorBuilder
where
F: FnOnce(TlsConnectorBuilder) -> TlsConnectorBuilder,
{
self.builder = call(self.builder);
self
}
#[inline]
pub fn timeout(mut self, timeout: Option<Duration>) -> ConnectorBuilder {
self.timeout = timeout;
self
}
#[inline]
pub fn timer(mut self, timer: Timer) -> ConnectorBuilder {
self.timer = timer;
self
}
#[inline]
pub fn verbose(mut self, enabled: bool) -> ConnectorBuilder {
self.config.verbose.0 = enabled;
self
}
#[inline]
pub fn tls_info(mut self, enabled: bool) -> ConnectorBuilder {
self.config.tls_info = enabled;
self
}
#[inline]
pub fn tcp_nodelay(mut self, enabled: bool) -> ConnectorBuilder {
self.config.nodelay = enabled;
self
}
pub fn build(
self,
tls_options: Option<TlsOptions>,
layers: Vec<BoxedConnectorLayer>,
) -> crate::Result<Connector> {
let timeout = TimeoutLayer::new(self.timer, self.timeout);
let service = TransportConnector {
config: self.config,
#[cfg(feature = "socks")]
resolver: self.resolver.clone(),
http: self.http,
tls: self
.builder
.build(tls_options.map(Cow::Owned).unwrap_or_default())?,
builder: Arc::new(self.builder),
};
if layers.is_empty() {
return Ok(Either::Left(timeout.layer(service)));
}
let service = layers.into_iter().fold(
BoxCloneSyncService::new(
ServiceBuilder::new()
.layer(MapRequestLayer::new(|request: Unnameable| request.0))
.service(service),
),
|service, layer| ServiceBuilder::new().layer(layer).service(service),
);
let service = ServiceBuilder::new()
.layer(timeout)
.service(service)
.map_err(map_timeout_to_connector_error);
let service = MapRequest::new(
BoxCloneSyncService::new(service),
Unnameable as fn(ConnectionDescriptor) -> Unnameable,
);
Ok(Either::Right(service))
}
}
impl TransportConnector {
fn build_https_connector(
&self,
https: bool,
descriptor: &ConnectionDescriptor,
) -> Result<HttpsConnector<HttpConnector>, BoxError> {
let mut http = self.http.clone();
if https && !self.config.nodelay {
http.set_nodelay(true);
}
if let Some(socket_opts) = descriptor.socket_bind_options() {
http.set_local_addresses(socket_opts.ipv4_address, socket_opts.ipv6_address);
#[cfg(any(
target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "solaris",
target_os = "tvos",
target_os = "visionos",
target_os = "watchos",
))]
if let Some(interface) = &socket_opts.interface {
http.set_interface(interface.clone());
}
}
let tls = descriptor
.tls_options()
.map(|opts| self.builder.build(Cow::Borrowed(opts)))
.transpose()?
.unwrap_or_else(|| self.tls.clone());
Ok(HttpsConnector::new(http, tls))
}
fn tunnel_conn_from_stream<IO>(&self, io: MaybeHttpsStream<IO>) -> Result<Conn, BoxError>
where
IO: AsyncConnWithInfo,
TlsConn<IO>: Connection,
SslStream<IO>: TlsInfoFactory,
{
let conn = match io {
MaybeHttpsStream::Http(stream) => Conn {
stream: self.config.verbose.wrap(stream),
tls_info: false,
proxy: None,
},
MaybeHttpsStream::Https(stream) => Conn {
stream: self.config.verbose.wrap(TlsConn { stream }),
tls_info: self.config.tls_info,
proxy: None,
},
};
Ok(conn)
}
fn conn_from_stream<IO, P>(&self, io: MaybeHttpsStream<IO>, proxy: P) -> Result<Conn, BoxError>
where
IO: AsyncConnWithInfo,
TlsConn<IO>: Connection,
SslStream<IO>: TlsInfoFactory,
P: Into<Option<Intercept>>,
{
let conn = match io {
MaybeHttpsStream::Http(stream) => self.config.verbose.wrap(stream),
MaybeHttpsStream::Https(stream) => self.config.verbose.wrap(TlsConn { stream }),
};
Ok(Conn {
stream: conn,
tls_info: self.config.tls_info,
proxy: proxy.into(),
})
}
async fn connect_auto_proxy<P: Into<Option<Intercept>>>(
self,
descriptor: ConnectionDescriptor,
proxy: P,
) -> Result<Conn, BoxError> {
let is_https = descriptor.uri().is_https();
let proxy = proxy.into();
trace!("connect with maybe proxy: {:?}", proxy);
let mut connector = self.build_https_connector(is_https, &descriptor)?;
if proxy.is_some() && is_https {
connector.no_alpn();
}
let io = connector.call(descriptor).await?;
if_tokio_rt!(block:{
if is_https && !self.config.nodelay {
io.as_ref().set_nodelay(false)?;
}
});
self.conn_from_stream(io, proxy)
}
async fn connect_via_proxy(
self,
mut descriptor: ConnectionDescriptor,
proxy: Intercepted,
) -> Result<Conn, BoxError> {
let uri = descriptor.uri().clone();
match proxy {
Intercepted::Proxy(proxy) => {
let is_https = uri.is_https();
let proxy_uri = proxy.uri().clone();
#[cfg(feature = "socks")]
{
use proxy::socks::{DnsResolve, SocksConnector, Version};
if let Some((version, dns_resolve)) = match proxy_uri.scheme_str() {
Some("socks4") => Some((Version::V4, DnsResolve::Local)),
Some("socks4a") => Some((Version::V4, DnsResolve::Remote)),
Some("socks5") => Some((Version::V5, DnsResolve::Local)),
Some("socks5h") => Some((Version::V5, DnsResolve::Remote)),
_ => None,
} {
trace!("connecting via SOCKS proxy: {:?}", proxy_uri);
let conn = {
let mut socks = SocksConnector::new(
proxy_uri,
self.http.clone(),
self.resolver.clone(),
);
socks.set_auth(proxy.raw_auth());
socks.set_version(version);
socks.set_dns_mode(dns_resolve);
socks.call(uri).await?
};
let mut connector = self.build_https_connector(is_https, &descriptor)?;
let io = connector
.call(EstablishedConn::new(conn, descriptor))
.await?;
if_tokio_rt!(block:{
if is_https && !self.config.nodelay {
io.as_ref().set_nodelay(false)?;
}
});
return self.tunnel_conn_from_stream(io);
}
}
if is_https {
trace!("tunneling over HTTP(s) proxy: {:?}", proxy_uri);
let mut connector = self.build_https_connector(is_https, &descriptor)?;
let tunneled = {
let mut tunnel =
proxy::tunnel::TunnelConnector::new(proxy_uri, connector.clone());
if let Some(auth) = proxy.basic_auth() {
tunnel = tunnel.with_auth(auth.clone());
}
if let Some(headers) = proxy.custom_headers() {
tunnel = tunnel.with_headers(headers.clone());
}
tunnel.call(uri).await?
};
let io = connector
.call(EstablishedConn::new(tunneled, descriptor))
.await?;
if_tokio_rt!(block:{
if !self.config.nodelay {
io.as_ref().as_ref().set_nodelay(false)?;
}
});
return self.tunnel_conn_from_stream(io);
}
*descriptor.uri_mut() = proxy_uri;
self.connect_auto_proxy(descriptor, proxy)
.await
.map_err(ProxyConnect)
.map_err(Into::into)
}
#[cfg(unix)]
Intercepted::Unix(unix_socket) => {
trace!("connecting via Unix socket: {:?}", unix_socket);
let mut connector =
HttpsConnector::new(UnixConnector::new(unix_socket), self.tls.clone());
if uri.is_https() {
let proxy_uri = http::Uri::from_static("http://localhost");
let tunneled = {
let mut tunnel =
proxy::tunnel::TunnelConnector::new(proxy_uri, connector.clone());
tunnel.call(uri).await?
};
let io = connector
.call(EstablishedConn::new(tunneled, descriptor))
.await?;
return self.tunnel_conn_from_stream(io);
}
let io = connector.call(descriptor).await?;
self.conn_from_stream(io, None)
}
}
}
async fn connect_auto(self, req: ConnectionDescriptor) -> Result<Conn, BoxError> {
debug!("starting new connection: {:?}", req.uri());
let intercepted = req
.proxy()
.and_then(|prox| prox.intercept(req.uri()))
.or_else(|| {
self.config
.proxies
.iter()
.find_map(|prox| prox.intercept(req.uri()))
});
if let Some(intercepted) = intercepted {
self.connect_via_proxy(req, intercepted).await
} else {
self.connect_auto_proxy(req, None).await
}
}
}
impl Service<ConnectionDescriptor> for TransportConnector {
type Response = Conn;
type Error = BoxError;
type Future = BoxFuture<'static, Result<Conn, BoxError>>;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
#[inline]
fn call(&mut self, descriptor: ConnectionDescriptor) -> Self::Future {
Box::pin(self.clone().connect_auto(descriptor))
}
}