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
42 changes: 26 additions & 16 deletions src/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub(crate) use self::conn::{Conn, Unnameable};
use crate::{
core::{
client::{
ConnRequest,
ConnExtra, ConnRequest,
connect::{self, Connected, Connection, proxy},
},
rt::{Read, ReadBufCursor, TokioIo, Write},
Expand Down Expand Up @@ -258,9 +258,8 @@ impl ConnectorService {
fn build_tls_connector(
&self,
mut http: HttpConnector,
req: &ConnRequest,
ex_data: &ConnExtra,
) -> Result<HttpsConnector<HttpConnector>, BoxError> {
let ex_data = req.ex_data();
http.set_connect_options(ex_data.tcp_connect_options().cloned());
let tls = match ex_data.tls_options() {
Some(opts) => self.tls_builder.build(opts)?,
Expand All @@ -284,7 +283,7 @@ impl ConnectorService {
http.set_nodelay(true);
}

let mut connector = self.build_tls_connector(http, &req)?;
let mut connector = self.build_tls_connector(http, req.ex_data())?;
let io = connector.call(req).await?;

// If the connection is HTTPS, wrap the TLS stream in a TlsConn for unified handling.
Expand Down Expand Up @@ -319,31 +318,36 @@ impl ConnectorService {

#[cfg(feature = "socks")]
{
use proxy::{DnsResolve, Socks, SocksVersion};
use proxy::socks::{DnsResolve, SocksConnector, Version};

if let Some((version, dns_resolve)) = match proxy.uri().scheme_str() {
Some("socks4") => Some((SocksVersion::V4, DnsResolve::Local)),
Some("socks4a") => Some((SocksVersion::V4, DnsResolve::Remote)),
Some("socks5") => Some((SocksVersion::V5, DnsResolve::Local)),
Some("socks5h") => Some((SocksVersion::V5, DnsResolve::Remote)),
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 mut socks = Socks::new_with_resolver(
// Create a SOCKS connector with the specified version and DNS resolution strategy.
let mut socks = SocksConnector::new_with_resolver(
proxy_uri,
self.http.clone(),
self.resolver.clone(),
proxy_uri.clone(),
)
.with_auth(proxy.raw_auth())
.with_version(version)
.with_local_dns(dns_resolve);

let conn = socks.call(uri.clone()).await?;
let is_https = uri.scheme() == Some(&Scheme::HTTPS);
let conn = socks.call(uri).await?;

return if uri.scheme() == Some(&Scheme::HTTPS) {
return if is_https {
trace!("socks HTTPS over proxy");
let mut connector = self.build_tls_connector(self.http.clone(), &req)?;

// Create a TLS connector for the established connection.
let mut connector =
self.build_tls_connector(self.http.clone(), req.ex_data())?;
let established_conn = EstablishedConn::new(req, conn);
let io = connector.call(established_conn).await?;

Expand All @@ -367,13 +371,17 @@ impl ConnectorService {
// Handle HTTPS proxy tunneling connection
if uri.scheme() == Some(&Scheme::HTTPS) {
trace!("tunneling HTTPS over HTTP proxy: {:?}", proxy_uri);
let mut connector = self.build_tls_connector(self.http.clone(), &req)?;

let mut tunnel = proxy::Tunnel::new(proxy_uri, connector.clone());
// Create a tunnel connector with the proxy URI and the HTTP connector.
let mut connector = self.build_tls_connector(self.http.clone(), req.ex_data())?;
let mut tunnel = proxy::tunnel::TunnelConnector::new(proxy_uri, connector.clone());

// If the proxy has basic authentication, add it to the tunnel.
if let Some(auth) = proxy.basic_auth() {
tunnel = tunnel.with_auth(auth.clone());
}

// If the proxy has custom headers, add them to the tunnel.
if let Some(headers) = proxy.custom_headers() {
tunnel = tunnel.with_headers(headers.clone());
}
Expand All @@ -383,6 +391,8 @@ impl ConnectorService {
let tunneled = tunnel.call(uri).await?;
let tunneled = TokioIo::new(tunneled);
let tunneled = TokioIo::new(tunneled);

// Create established connection with the tunneled stream.
let established_conn = EstablishedConn::new(req, tunneled);
let io = connector.call(established_conn).await?;

Expand Down
8 changes: 2 additions & 6 deletions src/core/client/connect/proxy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
//! Proxy helpers
#[cfg(feature = "socks")]
mod socks;
mod tunnel;

#[cfg(feature = "socks")]
pub use self::socks::{DnsResolve, Socks, SocksVersion};
pub use self::tunnel::Tunnel;
pub mod socks;
pub mod tunnel;
47 changes: 23 additions & 24 deletions src/core/client/connect/proxy/socks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl<C> From<tokio_socks::Error> for SocksError<C> {
/// Represents the SOCKS protocol version.
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum SocksVersion {
pub enum Version {
V4,
V5,
}
Expand Down Expand Up @@ -110,59 +110,58 @@ where
}
}

pub struct Socks<C, R = GaiResolver> {
pub struct SocksConnector<C, R = GaiResolver> {
inner: C,
resolver: R,
proxy: Uri,
proxy_dst: Uri,
auth: Option<(Bytes, Bytes)>,
version: SocksVersion,
version: Version,
dns_resolve: DnsResolve,
}

impl<C, R> Socks<C, R>
impl<C, R> SocksConnector<C, R>
where
R: Resolve + Clone,
{
/// Create a new SOCKS service with the given inner service, resolver, proxy destination,
/// and optional authentication credentials.
/// Create a new SOCKS connector with the given inner service.
///
/// The `proxy` should be a valid URI with a scheme of `socks5`, `socks5h`, `socks4`, or
/// `socks4a`.
/// This wraps an underlying connector, and stores the address of a
/// SOCKS proxy server.
///
/// The `auth` parameter is optional and can be used to provide a username and password for
/// SOCKS authentication. If provided, it should be a tuple containing the username and
/// password.
pub fn new_with_resolver(inner: C, resolver: R, proxy: Uri) -> Self {
Socks {
/// A `SocksConnector` can then be called with any destination. The `proxy_dst` passed to
/// `call` will not be used to create the underlying connection, but will
/// be used in a SOCKS handshake sent to the proxy destination.
pub fn new_with_resolver(proxy_dst: Uri, inner: C, resolver: R) -> Self {
SocksConnector {
inner,
resolver,
proxy,
version: SocksVersion::V5,
proxy_dst,
version: Version::V5,
dns_resolve: DnsResolve::Local,
auth: None,
}
}

/// Sets the authentication credentials for the SOCKS proxy connection.
pub fn with_auth(self, auth: Option<(Bytes, Bytes)>) -> Self {
Socks { auth, ..self }
SocksConnector { auth, ..self }
}

/// Sets whether to use the SOCKS5 protocol for the proxy connection.
pub fn with_version(self, version: SocksVersion) -> Self {
Socks { version, ..self }
pub fn with_version(self, version: Version) -> Self {
SocksConnector { version, ..self }
}

/// Sets whether to resolve DNS locally or let the proxy handle DNS resolution.
pub fn with_local_dns(self, dns_resolve: DnsResolve) -> Self {
Socks {
SocksConnector {
dns_resolve,
..self
}
}
}

impl<C, R> Service<Uri> for Socks<C, R>
impl<C, R> Service<Uri> for SocksConnector<C, R>
where
C: Service<Uri>,
C::Future: Send + 'static,
Expand All @@ -180,7 +179,7 @@ where
}

fn call(&mut self, dst: Uri) -> Self::Future {
let connecting = self.inner.call(self.proxy.clone());
let connecting = self.inner.call(self.proxy_dst.clone());

let version = self.version;
let dns_resolve = self.dns_resolve;
Expand Down Expand Up @@ -214,12 +213,12 @@ where
};

match version {
SocksVersion::V4 => {
Version::V4 => {
// For SOCKS4, we connect directly to the target address.
let stream = Socks4Stream::connect_with_socket(socket, target_addr).await?;
Ok(stream.into_inner().into_inner())
}
SocksVersion::V5 => {
Version::V5 => {
// For SOCKS5, we need to handle authentication if provided.
// The `auth` is an optional tuple of (username, password).
let stream = match auth {
Expand Down
14 changes: 7 additions & 7 deletions src/core/client/connect/proxy/tunnel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::core::{
/// another connector, and after getting an underlying connection, it creates
/// an HTTP CONNECT tunnel over it.
#[derive(Debug)]
pub struct Tunnel<C> {
pub struct TunnelConnector<C> {
headers: Headers,
inner: C,
proxy_dst: Uri,
Expand Down Expand Up @@ -60,13 +60,13 @@ pin_project! {

type BoxTunneling<T> = Pin<Box<dyn Future<Output = Result<T, TunnelError>> + Send>>;

impl<C> Tunnel<C> {
/// Create a new Tunnel service.
impl<C> TunnelConnector<C> {
/// Create a new tunnel connector.
///
/// This wraps an underlying connector, and stores the address of a
/// tunneling proxy server.
///
/// A `Tunnel` can then be called with any destination. The `dst` passed to
/// A `TunnelConnector` can then be called with any destination. The `proxy_dst` passed to
/// `call` will not be used to create the underlying connection, but will
/// be used in an HTTP CONNECT request sent to the proxy destination.
pub fn new(proxy_dst: Uri, connector: C) -> Self {
Expand Down Expand Up @@ -119,7 +119,7 @@ impl<C> Tunnel<C> {
}
}

impl<C> Service<Uri> for Tunnel<C>
impl<C> Service<Uri> for TunnelConnector<C>
where
C: Service<Uri>,
C::Future: Send + 'static,
Expand Down Expand Up @@ -270,7 +270,7 @@ mod tests {
};
use tower::Service;

use super::Tunnel;
use super::TunnelConnector;
use crate::core::client::connect::HttpConnector;

#[cfg(not(miri))]
Expand All @@ -280,7 +280,7 @@ mod tests {
let addr = tcp.local_addr().expect("local_addr");

let proxy_dst = format!("http://{addr}").parse().expect("uri");
let mut connector = Tunnel::new(proxy_dst, HttpConnector::new());
let mut connector = TunnelConnector::new(proxy_dst, HttpConnector::new());
let t1 = tokio::spawn(async move {
let _conn = connector
.call("https://hyper.rs".parse().unwrap())
Expand Down
Loading