feat(config): support domain hosts in ProxyList entries
This commit is contained in:
1 parent
0f9f11cd67
commit
ea80dd4fa9
8 files changed
+123
-28
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in new issue
Block a user