feat(config): support domain hosts in ProxyList entries

This commit is contained in:
tianrking committed 2026-03-20 18:15:54 +08:00
1 parent 0f9f11cd67
commit ea80dd4fa9
8 files changed
+123 -28

No files matched your search

+1
View File
@@ -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
+1
View File
@@ -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
+20 -6
View File
@@ -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),
+6 -6
View File
@@ -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
+41 -9
View File
@@ -113,15 +113,13 @@ impl ConfigParser {
/// Parse SOCKS5 from environment variables
fn parse_socks5_env(&self, host: &str, port: u16) -> Result<Config> {
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);
}
}
+48 -2
View File
@@ -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<Self, Self::Err> {
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
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<String>, port: u16, proxy_type: ProxyType) -> Self {
let host = host.into();
let ip = host
.parse::<Ipv4Addr>()
.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<Ipv4Addr> {
if self.host.eq_ignore_ascii_case("localhost") {
return Ok(Ipv4Addr::new(127, 0, 0, 1));
}
if let Ok(ip) = self.host.parse::<Ipv4Addr>() {
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<SocketAddrV4> {
Ok(SocketAddrV4::new(self.resolve_ipv4()?, self.port))
}
}
/// Local network for bypass
+4 -3
View File
@@ -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
)));
}
}
+2 -2
View File
@@ -84,7 +84,7 @@ impl TargetAddress {
/// Connect to a proxy server
pub fn connect_to_proxy(proxy: &ProxyData, timeout: Duration) -> Result<std::net::TcpStream> {
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],