From b59b5e1289f8ee2a6dcaa39ff8a8803faf120f1e Mon Sep 17 00:00:00 2001 From: Jonathan Alter Date: Tue, 20 Aug 2024 17:53:25 -0500 Subject: [PATCH] feat: Add support for SOCKS4 (#610) --- src/connect.rs | 52 +++++++++++++++++++++++++++++--------------------- src/proxy.rs | 38 +++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/connect.rs b/src/connect.rs index ff76c57f8..bf681f9b5 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -206,6 +206,7 @@ impl Connector { #[cfg(feature = "socks")] async fn connect_socks(&self, dst: Uri, proxy: ProxyScheme) -> Result { let dns = match proxy { + ProxyScheme::Socks4 { .. } => socks::DnsResolve::Local, ProxyScheme::Socks5 { remote_dns: false, .. } => socks::DnsResolve::Local, @@ -367,6 +368,8 @@ impl Connector { ProxyScheme::Http { host, auth } => (into_uri(Scheme::HTTP, host), auth), ProxyScheme::Https { host, auth } => (into_uri(Scheme::HTTPS, host), auth), #[cfg(feature = "socks")] + ProxyScheme::Socks4 { .. } => return self.connect_socks(dst, proxy_scheme).await, + #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => return self.connect_socks(dst, proxy_scheme).await, }; @@ -1031,7 +1034,7 @@ mod socks { use http::Uri; use tokio::net::TcpStream; - use tokio_socks::tcp::Socks5Stream; + use tokio_socks::tcp::{Socks4Stream, Socks5Stream}; use super::{BoxError, Scheme}; use crate::proxy::ProxyScheme; @@ -1064,28 +1067,33 @@ mod socks { } } - let (socket_addr, auth) = match proxy { - ProxyScheme::Socks5 { addr, auth, .. } => (addr, auth), - _ => unreachable!(), - }; - - // Get a Tokio TcpStream - let stream = if let Some((username, password)) = auth { - Socks5Stream::connect_with_password( - socket_addr, - (host.as_str(), port), - &username, - &password, - ) - .await - .map_err(|e| format!("socks connect error: {e}"))? - } else { - Socks5Stream::connect(socket_addr, (host.as_str(), port)) - .await - .map_err(|e| format!("socks connect error: {e}"))? - }; + match proxy { + ProxyScheme::Socks4 { addr } => { + let stream = Socks4Stream::connect(addr, (host.as_str(), port)) + .await + .map_err(|e| format!("socks connect error: {e}"))?; + Ok(stream.into_inner()) + } + ProxyScheme::Socks5 { addr, ref auth, .. } => { + let stream = if let Some((username, password)) = auth { + Socks5Stream::connect_with_password( + addr, + (host.as_str(), port), + &username, + &password, + ) + .await + .map_err(|e| format!("socks connect error: {e}"))? + } else { + Socks5Stream::connect(addr, (host.as_str(), port)) + .await + .map_err(|e| format!("socks connect error: {e}"))? + }; - Ok(stream.into_inner()) + Ok(stream.into_inner()) + } + _ => unreachable!(), + } } } diff --git a/src/proxy.rs b/src/proxy.rs index 5be207a8a..880e2452e 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -106,6 +106,8 @@ pub enum ProxyScheme { host: http::uri::Authority, }, #[cfg(feature = "socks")] + Socks4 { addr: SocketAddr }, + #[cfg(feature = "socks")] Socks5 { addr: SocketAddr, auth: Option<(String, String)>, @@ -577,6 +579,16 @@ impl ProxyScheme { }) } + /// Proxy traffic via the specified socket address over SOCKS4 + /// + /// # Note + /// + /// Current SOCKS4 support is provided via blocking IO. + #[cfg(feature = "socks")] + fn socks4(addr: SocketAddr) -> crate::Result { + Ok(ProxyScheme::Socks4 { addr }) + } + /// Proxy traffic via the specified socket address over SOCKS5 /// /// # Note @@ -628,6 +640,10 @@ impl ProxyScheme { *auth = Some(header); } #[cfg(feature = "socks")] + ProxyScheme::Socks4 { .. } => { + panic!("Socks4 is not supported for this method") + } + #[cfg(feature = "socks")] ProxyScheme::Socks5 { ref mut auth, .. } => { *auth = Some((username.into(), password.into())); } @@ -643,8 +659,12 @@ impl ProxyScheme { *auth = Some(header_value); } #[cfg(feature = "socks")] + ProxyScheme::Socks4 { .. } => { + panic!("Socks4 is not supported for this method") + } + #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => { - panic!("Socks is not supported for this method") + panic!("Socks5 is not supported for this method") } } } @@ -662,6 +682,8 @@ impl ProxyScheme { } } #[cfg(feature = "socks")] + ProxyScheme::Socks4 { .. } => {} + #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => {} } @@ -670,7 +692,7 @@ impl ProxyScheme { /// Convert a URL into a proxy scheme /// - /// Supported schemes: HTTP, HTTPS, (SOCKS5, SOCKS5H if `socks` feature is enabled). + /// Supported schemes: HTTP, HTTPS, (SOCKS4, SOCKS5, SOCKS5H if `socks` feature is enabled). // Private for now... fn parse(url: Url) -> crate::Result { use url::Position; @@ -680,7 +702,7 @@ impl ProxyScheme { let to_addr = || { let addrs = url .socket_addrs(|| match url.scheme() { - "socks5" | "socks5h" => Some(1080), + "socks4" | "socks5" | "socks5h" => Some(1080), _ => None, }) .map_err(crate::error::builder)?; @@ -694,6 +716,8 @@ impl ProxyScheme { "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?, "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?, #[cfg(feature = "socks")] + "socks4" => Self::socks4(to_addr()?)?, + #[cfg(feature = "socks")] "socks5" => Self::socks5(to_addr()?)?, #[cfg(feature = "socks")] "socks5h" => Self::socks5h(to_addr()?)?, @@ -715,6 +739,8 @@ impl ProxyScheme { ProxyScheme::Http { .. } => "http", ProxyScheme::Https { .. } => "https", #[cfg(feature = "socks")] + ProxyScheme::Socks4 { .. } => "socks4", + #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => "socks5", } } @@ -725,6 +751,8 @@ impl ProxyScheme { ProxyScheme::Http { host, .. } => host.as_str(), ProxyScheme::Https { host, .. } => host.as_str(), #[cfg(feature = "socks")] + ProxyScheme::Socks4 { .. } => panic!("socks4"), + #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => panic!("socks5"), } } @@ -736,6 +764,10 @@ impl fmt::Debug for ProxyScheme { ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{host}"), ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{host}"), #[cfg(feature = "socks")] + ProxyScheme::Socks4 { addr } => { + write!(f, "socks4://{addr}") + } + #[cfg(feature = "socks")] ProxyScheme::Socks5 { addr, auth: _auth,