@@ -3,7 +3,10 @@
use anyhow::{Context as _, Result};
use http_client::Url;
use tokio::net::TcpStream;
-use tokio_socks::tcp::{Socks4Stream, Socks5Stream};
+use tokio_socks::{
+ IntoTargetAddr, TargetAddr,
+ tcp::{Socks4Stream, Socks5Stream},
+};
use super::AsyncReadWrite;
@@ -23,8 +26,14 @@ pub(super) struct Socks5Authorization<'a> {
/// V4 allows idenfication using a user_id
/// V5 allows authorization using a username and password
pub(super) enum SocksVersion<'a> {
- V4(Option<Socks4Identification<'a>>),
- V5(Option<Socks5Authorization<'a>>),
+ V4 {
+ local_dns: bool,
+ identification: Option<Socks4Identification<'a>>,
+ },
+ V5 {
+ local_dns: bool,
+ authorization: Option<Socks5Authorization<'a>>,
+ },
}
pub(super) fn parse_socks_proxy<'t>(scheme: &str, proxy: &'t Url) -> SocksVersion<'t> {
@@ -33,13 +42,19 @@ pub(super) fn parse_socks_proxy<'t>(scheme: &str, proxy: &'t Url) -> SocksVersio
"" => None,
username => Some(Socks4Identification { user_id: username }),
};
- SocksVersion::V4(identification)
+ SocksVersion::V4 {
+ local_dns: scheme != "socks4a",
+ identification,
+ }
} else {
let authorization = proxy.password().map(|password| Socks5Authorization {
username: proxy.username(),
password,
});
- SocksVersion::V5(authorization)
+ SocksVersion::V5 {
+ local_dns: scheme != "socks5h",
+ authorization,
+ }
}
}
@@ -48,26 +63,58 @@ pub(super) async fn connect_socks_proxy_stream(
socks_version: SocksVersion<'_>,
rpc_host: (&str, u16),
) -> Result<Box<dyn AsyncReadWrite>> {
+ let rpc_host = rpc_host
+ .into_target_addr()
+ .context("Failed to parse target addr")?;
+
+ let local_dns = match &socks_version {
+ SocksVersion::V4 { local_dns, .. } => local_dns,
+ SocksVersion::V5 { local_dns, .. } => local_dns,
+ };
+ let rpc_host = match (rpc_host, local_dns) {
+ (TargetAddr::Domain(domain, port), true) => {
+ let ip_addr = tokio::net::lookup_host((domain.as_ref(), port))
+ .await
+ .with_context(|| format!("Failed to lookup domain {}", domain))?
+ .next()
+ .ok_or_else(|| anyhow::anyhow!("Failed to lookup domain {}", domain))?;
+ TargetAddr::Ip(ip_addr)
+ }
+ (rpc_host, _) => rpc_host,
+ };
+
match socks_version {
- SocksVersion::V4(None) => {
+ SocksVersion::V4 {
+ identification: None,
+ ..
+ } => {
let socks = Socks4Stream::connect_with_socket(stream, rpc_host)
.await
.context("error connecting to socks")?;
Ok(Box::new(socks))
}
- SocksVersion::V4(Some(Socks4Identification { user_id })) => {
+ SocksVersion::V4 {
+ identification: Some(Socks4Identification { user_id }),
+ ..
+ } => {
let socks = Socks4Stream::connect_with_userid_and_socket(stream, rpc_host, user_id)
.await
.context("error connecting to socks")?;
Ok(Box::new(socks))
}
- SocksVersion::V5(None) => {
+ SocksVersion::V5 {
+ authorization: None,
+ ..
+ } => {
let socks = Socks5Stream::connect_with_socket(stream, rpc_host)
.await
.context("error connecting to socks")?;
Ok(Box::new(socks))
}
- SocksVersion::V5(Some(Socks5Authorization { username, password })) => {
+ SocksVersion::V5 {
+ authorization: Some(Socks5Authorization { username, password }),
+ ..
+ } => {
let socks = Socks5Stream::connect_with_password_and_socket(
stream, rpc_host, username, password,
)
@@ -90,7 +137,13 @@ mod tests {
let scheme = proxy.scheme();
let version = parse_socks_proxy(scheme, &proxy);
- assert!(matches!(version, SocksVersion::V4(None)))
+ assert!(matches!(
+ version,
+ SocksVersion::V4 {
+ local_dns: true,
+ identification: None
+ }
+ ))
}
#[test]
@@ -101,7 +154,25 @@ mod tests {
let version = parse_socks_proxy(scheme, &proxy);
assert!(matches!(
version,
- SocksVersion::V4(Some(Socks4Identification { user_id: "userid" }))
+ SocksVersion::V4 {
+ local_dns: true,
+ identification: Some(Socks4Identification { user_id: "userid" })
+ }
+ ))
+ }
+
+ #[test]
+ fn parse_socks4_with_remote_dns() {
+ let proxy = Url::parse("socks4a://proxy.example.com:1080").unwrap();
+ let scheme = proxy.scheme();
+
+ let version = parse_socks_proxy(scheme, &proxy);
+ assert!(matches!(
+ version,
+ SocksVersion::V4 {
+ local_dns: false,
+ identification: None
+ }
))
}
@@ -111,7 +182,13 @@ mod tests {
let scheme = proxy.scheme();
let version = parse_socks_proxy(scheme, &proxy);
- assert!(matches!(version, SocksVersion::V5(None)))
+ assert!(matches!(
+ version,
+ SocksVersion::V5 {
+ local_dns: true,
+ authorization: None
+ }
+ ))
}
#[test]
@@ -122,10 +199,28 @@ mod tests {
let version = parse_socks_proxy(scheme, &proxy);
assert!(matches!(
version,
- SocksVersion::V5(Some(Socks5Authorization {
- username: "username",
- password: "password"
- }))
+ SocksVersion::V5 {
+ local_dns: true,
+ authorization: Some(Socks5Authorization {
+ username: "username",
+ password: "password"
+ })
+ }
+ ))
+ }
+
+ #[test]
+ fn parse_socks5_with_remote_dns() {
+ let proxy = Url::parse("socks5h://proxy.example.com:1080").unwrap();
+ let scheme = proxy.scheme();
+
+ let version = parse_socks_proxy(scheme, &proxy);
+ assert!(matches!(
+ version,
+ SocksVersion::V5 {
+ local_dns: false,
+ authorization: None
+ }
))
}
}