diff --git a/README.md b/README.md index b2990f4..f9b48bd 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ max_chain_retries 8 [ProxyList] socks5 127.0.0.1 1080 +# socks5 proxy.example.com 1080 # socks4 127.0.0.1 1081 # http 127.0.0.1 8080 diff --git a/README.zh-CN.md b/README.zh-CN.md index 708ae19..fbc6340 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -118,6 +118,7 @@ max_chain_retries 8 [ProxyList] socks5 127.0.0.1 1080 +# socks5 proxy.example.com 1080 # socks4 127.0.0.1 1081 # http 127.0.0.1 8080 diff --git a/proxychains-bin/src/main.rs b/proxychains-bin/src/main.rs index 279a63a..c31791c 100644 --- a/proxychains-bin/src/main.rs +++ b/proxychains-bin/src/main.rs @@ -8,7 +8,7 @@ use std::env; use std::io::ErrorKind; -use std::net::{SocketAddr, SocketAddrV4, TcpStream}; +use std::net::{SocketAddr, TcpStream}; use std::path::PathBuf; use std::process; use std::time::SystemTime; @@ -211,7 +211,7 @@ fn print_check_summary(config: &Config, args: &Args) { " {}. {} {}:{} ({})", idx + 1, proxy.proxy_type, - proxy.ip, + proxy.host, proxy.port, auth ); @@ -228,8 +228,22 @@ fn run_probe(config: &Config, args: &Args) -> usize { let selected_group = args.group.as_deref().unwrap_or("default/all").to_string(); for (idx, proxy) in config.proxies.iter().enumerate() { - let target_v4 = SocketAddrV4::new(proxy.ip, proxy.port); - let target = SocketAddr::V4(target_v4); + let target = match proxy.resolved_socket_addr() { + Ok(v4) => SocketAddr::V4(v4), + Err(e) => { + failed += 1; + results.push(ProbeNode { + index: idx + 1, + proxy_type: proxy.proxy_type.to_string(), + address: format!("{}:{}", proxy.host, proxy.port), + ok: false, + latency_ms: 0, + failure_type: Some("resolve_error".to_string()), + error: Some(e.to_string()), + }); + continue; + } + }; let start = Instant::now(); match TcpStream::connect_timeout(&target, timeout) { Ok(stream) => { @@ -238,7 +252,7 @@ fn run_probe(config: &Config, args: &Args) -> usize { results.push(ProbeNode { index: idx + 1, proxy_type: proxy.proxy_type.to_string(), - address: format!("{}:{}", proxy.ip, proxy.port), + address: format!("{}:{}", proxy.host, proxy.port), ok: true, latency_ms: elapsed, failure_type: None, @@ -252,7 +266,7 @@ fn run_probe(config: &Config, args: &Args) -> usize { results.push(ProbeNode { index: idx + 1, proxy_type: proxy.proxy_type.to_string(), - address: format!("{}:{}", proxy.ip, proxy.port), + address: format!("{}:{}", proxy.host, proxy.port), ok: false, latency_ms: elapsed, failure_type: Some(failure_type), diff --git a/proxychains/src/chain/manager.rs b/proxychains/src/chain/manager.rs index b8695bb..a1a2a7a 100644 --- a/proxychains/src/chain/manager.rs +++ b/proxychains/src/chain/manager.rs @@ -122,11 +122,11 @@ impl ChainManager { // Chain through remaining proxies for i in 1..proxies.len() { let next_proxy = &proxies[i]; - let next_target = TargetAddress::from_ip(IpAddr::V4(next_proxy.ip)); + let next_target = TargetAddress::from_domain(next_proxy.host.clone()); info!( "Chaining to proxy {}:{}", - next_proxy.ip, next_proxy.port + next_proxy.host, next_proxy.port ); if let Err(e) = tunnel_through_proxy( @@ -220,11 +220,11 @@ impl ChainManager { // Chain through remaining proxies while let Some(next_idx) = self.find_alive_proxy(proxies, &mut offset) { let next_proxy = &proxies[next_idx]; - let next_target = TargetAddress::from_ip(IpAddr::V4(next_proxy.ip)); + let next_target = TargetAddress::from_domain(next_proxy.host.clone()); debug!( "Dynamic chain: connecting to proxy {}:{}", - next_proxy.ip, next_proxy.port + next_proxy.host, next_proxy.port ); if let Err(e) = tunnel_through_proxy( @@ -323,7 +323,7 @@ impl ChainManager { // Chain through remaining selected proxies for &next_idx in &selected_indices[1..] { let next_proxy = &proxies[next_idx]; - let next_target = TargetAddress::from_ip(IpAddr::V4(next_proxy.ip)); + let next_target = TargetAddress::from_domain(next_proxy.host.clone()); if let Err(e) = tunnel_through_proxy( &mut stream, @@ -380,7 +380,7 @@ impl ChainManager { let proxy = &proxies[selected_idx]; debug!( "Load balance: selected proxy {}:{}", - proxy.ip, proxy.port + proxy.host, proxy.port ); // Connect to proxy diff --git a/proxychains/src/config/parser.rs b/proxychains/src/config/parser.rs index 016d82b..4f32e3b 100644 --- a/proxychains/src/config/parser.rs +++ b/proxychains/src/config/parser.rs @@ -113,15 +113,13 @@ impl ConfigParser { /// Parse SOCKS5 from environment variables fn parse_socks5_env(&self, host: &str, port: u16) -> Result { - let ip: Ipv4Addr = host.parse().map_err(|_| { - Error::Config(format!("Invalid SOCKS5 host: {}", host)) - })?; - let mut config = Config::default(); config.proxy_dns = env::var(ENV_DNS).is_ok(); config.quiet_mode = env::var(ENV_QUIET_MODE).is_ok(); - config.proxies.push(ProxyData::new(ip, port, ProxyType::Socks5)); + config + .proxies + .push(ProxyData::new_host(host.to_string(), port, ProxyType::Socks5)); Ok(config) } @@ -364,15 +362,16 @@ impl ConfigParser { Error::Config(e) })?; - let ip: Ipv4Addr = parts[1].parse().map_err(|_| { - Error::Config(format!("Invalid proxy host: {}", parts[1])) - })?; + let host = parts[1].to_string(); + if host.is_empty() { + return Err(Error::Config("Invalid proxy host: empty".to_string())); + } let port: u16 = parts[2].parse().map_err(|_| { Error::Config(format!("Invalid proxy port: {}", parts[2])) })?; - let mut proxy = ProxyData::new(ip, port, proxy_type); + let mut proxy = ProxyData::new_host(host, port, proxy_type); // Parse optional credentials if parts.len() >= 5 { @@ -543,4 +542,37 @@ socks5 127.0.0.1 1080 let _ = fs::remove_file(path); } + + #[test] + fn test_parse_proxy_domain_host() { + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!("proxychains_domain_host_{}.conf", ts)); + let content = r#" +[ProxyList] +socks5 proxy.example.com 1080 +"#; + fs::write(&path, content).unwrap(); + + let config = ConfigParser::new().with_path(path.clone()).parse().unwrap(); + assert_eq!(config.proxies.len(), 1); + assert_eq!(config.proxies[0].host, "proxy.example.com"); + assert_eq!(config.proxies[0].port, 1080); + + let _ = fs::remove_file(path); + } + + #[test] + fn test_parse_socks5_env_domain_host() { + std::env::set_var(ENV_SOCKS5_HOST, "proxy.example.com"); + std::env::set_var(ENV_SOCKS5_PORT, "1080"); + let config = ConfigParser::new().parse().unwrap(); + assert_eq!(config.proxies.len(), 1); + assert_eq!(config.proxies[0].host, "proxy.example.com"); + assert_eq!(config.proxies[0].port, 1080); + std::env::remove_var(ENV_SOCKS5_HOST); + std::env::remove_var(ENV_SOCKS5_PORT); + } } diff --git a/proxychains/src/config/types.rs b/proxychains/src/config/types.rs index e220399..3732ea5 100644 --- a/proxychains/src/config/types.rs +++ b/proxychains/src/config/types.rs @@ -1,7 +1,8 @@ //! Configuration types for proxychains -use std::net::{IpAddr, Ipv4Addr, SocketAddrV4}; +use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, ToSocketAddrs}; use std::time::Duration; +use crate::error::{Error, Result}; /// Proxy protocol type #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -27,7 +28,7 @@ impl std::fmt::Display for ProxyType { impl std::str::FromStr for ProxyType { type Err = String; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> std::result::Result { match s.to_lowercase().as_str() { "socks5" | "socks5h" => Ok(ProxyType::Socks5), "socks4" | "socks4a" => Ok(ProxyType::Socks4), @@ -74,6 +75,8 @@ impl std::fmt::Display for ChainType { /// Proxy data structure #[derive(Debug, Clone)] pub struct ProxyData { + /// Proxy hostname (domain or IPv4 literal) + pub host: String, /// Proxy IP address pub ip: Ipv4Addr, /// Proxy port @@ -91,6 +94,7 @@ pub struct ProxyData { impl Default for ProxyData { fn default() -> Self { Self { + host: "127.0.0.1".to_string(), ip: Ipv4Addr::new(127, 0, 0, 1), port: 1080, proxy_type: ProxyType::default(), @@ -105,6 +109,22 @@ impl ProxyData { /// Create a new proxy data pub fn new(ip: Ipv4Addr, port: u16, proxy_type: ProxyType) -> Self { Self { + host: ip.to_string(), + ip, + port, + proxy_type, + ..Default::default() + } + } + + /// Create a proxy from host string (domain or IPv4 literal). + pub fn new_host(host: impl Into, port: u16, proxy_type: ProxyType) -> Self { + let host = host.into(); + let ip = host + .parse::() + .unwrap_or_else(|_| Ipv4Addr::new(0, 0, 0, 0)); + Self { + host, ip, port, proxy_type, @@ -123,6 +143,32 @@ impl ProxyData { pub fn socket_addr(&self) -> SocketAddrV4 { SocketAddrV4::new(self.ip, self.port) } + + /// Resolve configured host to IPv4 address. + pub fn resolve_ipv4(&self) -> Result { + if self.host.eq_ignore_ascii_case("localhost") { + return Ok(Ipv4Addr::new(127, 0, 0, 1)); + } + if let Ok(ip) = self.host.parse::() { + return Ok(ip); + } + + let mut addrs = (self.host.as_str(), self.port) + .to_socket_addrs() + .map_err(|e| Error::Dns(format!("Failed to resolve proxy host {}: {}", self.host, e)))?; + + addrs + .find_map(|addr| match addr.ip() { + IpAddr::V4(v4) => Some(v4), + IpAddr::V6(_) => None, + }) + .ok_or_else(|| Error::Dns(format!("No IPv4 address for proxy host {}", self.host))) + } + + /// Resolve proxy host and build socket address. + pub fn resolved_socket_addr(&self) -> Result { + Ok(SocketAddrV4::new(self.resolve_ipv4()?, self.port)) + } } /// Local network for bypass diff --git a/proxychains/src/hook/hooks_windows.rs b/proxychains/src/hook/hooks_windows.rs index 3c0dcbc..2ed32ec 100644 --- a/proxychains/src/hook/hooks_windows.rs +++ b/proxychains/src/hook/hooks_windows.rs @@ -153,7 +153,8 @@ fn make_sockaddr_in6_mapped_bytes(ip: Ipv4Addr, port: u16) -> [u8; 28] { } unsafe fn connect_socket_to_proxy(sock: usize, proxy: &ProxyData) -> Result<()> { - let sockaddr = make_sockaddr_in(proxy.ip, proxy.port); + let resolved_ip = proxy.resolve_ipv4()?; + let sockaddr = make_sockaddr_in(resolved_ip, proxy.port); let ret = original_connect( sock, &sockaddr as *const SOCKADDR_IN as *const c_void, @@ -171,7 +172,7 @@ unsafe fn connect_socket_to_proxy(sock: usize, proxy: &ProxyData) -> Result<()> // IPv6 sockets cannot use IPv4 sockaddr directly. Retry using an IPv4-mapped // IPv6 destination (::ffff:a.b.c.d). - let mapped = make_sockaddr_in6_mapped_bytes(proxy.ip, proxy.port); + let mapped = make_sockaddr_in6_mapped_bytes(resolved_ip, proxy.port); let mapped_ret = original_connect( sock, mapped.as_ptr() as *const c_void, @@ -187,7 +188,7 @@ unsafe fn connect_socket_to_proxy(sock: usize, proxy: &ProxyData) -> Result<()> } return Err(Error::ProxyConnection(format!( "Failed to connect to proxy {}:{} (WSA {}, retry WSA {})", - proxy.ip, proxy.port, wsa_error, mapped_err + proxy.host, proxy.port, wsa_error, mapped_err ))); } } diff --git a/proxychains/src/proxy/mod.rs b/proxychains/src/proxy/mod.rs index 2f42d85..61afa63 100644 --- a/proxychains/src/proxy/mod.rs +++ b/proxychains/src/proxy/mod.rs @@ -84,7 +84,7 @@ impl TargetAddress { /// Connect to a proxy server pub fn connect_to_proxy(proxy: &ProxyData, timeout: Duration) -> Result { - let addr = proxy.socket_addr(); + let addr = proxy.resolved_socket_addr()?; connect_with_timeout(&addr, timeout) } @@ -177,7 +177,7 @@ pub fn establish_proxy_chain( } else { // Intermediate hop - connect to next proxy let next_proxy = &proxies[i]; - let next_target = TargetAddress::from_ip(IpAddr::V4(next_proxy.ip)); + let next_target = TargetAddress::from_domain(next_proxy.host.clone()); tunnel_through_proxy( &mut stream, &proxies[i - 1],