diff --git a/README.md b/README.md index bf3f3c2..4388174 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Recommendation: - Discovery/validation: - `--list-groups` - `--check` + - `--probe` (quick per-node reachability/latency check) - DNS: - `proxy_dns` - compatibility aliases: `proxy_dns_old`, `proxy_dns_daemon` @@ -88,6 +89,8 @@ Validate config and groups: ```bash proxychains4 -f ./proxychains.conf --list-groups proxychains4 -f ./proxychains.conf --group jp --check +proxychains4 -f ./proxychains.conf --group jp --probe +proxychains4 -f ./proxychains.conf --probe --probe-timeout-ms 1200 ``` ## Example Config diff --git a/README.zh-CN.md b/README.zh-CN.md index bc69ce2..ab1698c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -40,6 +40,7 @@ - 配置发现/校验: - `--list-groups` - `--check` + - `--probe`(快速探测每个节点连通性与时延) - DNS: - `proxy_dns` - 兼容别名:`proxy_dns_old`、`proxy_dns_daemon` @@ -88,6 +89,8 @@ proxychains4.exe curl https://ifconfig.me ```bash proxychains4 -f ./proxychains.conf --list-groups proxychains4 -f ./proxychains.conf --group jp --check +proxychains4 -f ./proxychains.conf --group jp --probe +proxychains4 -f ./proxychains.conf --probe --probe-timeout-ms 1200 ``` ## 示例配置 diff --git a/proxychains-bin/src/main.rs b/proxychains-bin/src/main.rs index f3d1664..79a26e3 100644 --- a/proxychains-bin/src/main.rs +++ b/proxychains-bin/src/main.rs @@ -7,8 +7,10 @@ //! - Windows: Uses DLL injection use std::env; +use std::net::{SocketAddr, SocketAddrV4, TcpStream}; use std::path::PathBuf; use std::process; +use std::time::{Duration, Instant}; use clap::Parser; use tracing::{debug, error, info, Level}; @@ -47,9 +49,17 @@ struct Args { #[arg(long)] check: bool, + /// Probe configured proxy nodes and print per-node reachability + #[arg(long)] + probe: bool, + + /// Probe timeout in milliseconds (default: config tcp_connect_time_out) + #[arg(long, value_name = "MS")] + probe_timeout_ms: Option, + /// The command to run #[arg( - required_unless_present_any = ["list_groups", "check"], + required_unless_present_any = ["list_groups", "check", "probe"], trailing_var_arg = true )] command: Vec, @@ -121,6 +131,11 @@ fn main() { process::exit(0); } + if args.probe { + let failed = run_probe(&config, &args); + process::exit(if failed == 0 { 0 } else { 2 }); + } + // Execute the command with platform-specific injection match execute_command(&args, &config) { Ok(status) => { @@ -190,6 +205,62 @@ fn print_check_summary(config: &Config, args: &Args) { } } +fn run_probe(config: &Config, args: &Args) -> usize { + let timeout = args + .probe_timeout_ms + .map(Duration::from_millis) + .unwrap_or(config.tcp_connect_timeout); + let mut failed = 0usize; + + println!("Proxy probe:"); + println!(" timeout_ms={}", timeout.as_millis()); + println!( + " group={}", + args.group.as_deref().unwrap_or("default/all") + ); + + for (idx, proxy) in config.proxies.iter().enumerate() { + let target_v4 = SocketAddrV4::new(proxy.ip, proxy.port); + let target = SocketAddr::V4(target_v4); + let start = Instant::now(); + match TcpStream::connect_timeout(&target, timeout) { + Ok(stream) => { + let elapsed = start.elapsed().as_millis(); + let _ = stream.shutdown(std::net::Shutdown::Both); + println!( + " [{}] OK {:<6} {}:{} {} ms", + idx + 1, + proxy.proxy_type, + proxy.ip, + proxy.port, + elapsed + ); + } + Err(e) => { + failed += 1; + let elapsed = start.elapsed().as_millis(); + println!( + " [{}] FAIL {:<6} {}:{} {} ms ({})", + idx + 1, + proxy.proxy_type, + proxy.ip, + proxy.port, + elapsed, + e + ); + } + } + } + + println!( + "Probe summary: total={}, ok={}, fail={}", + config.proxies.len(), + config.proxies.len().saturating_sub(failed), + failed + ); + failed +} + /// Set proxychains-specific environment variables fn set_proxychains_env(config: &Config, args: &Args) { if args.quiet { @@ -425,4 +496,13 @@ mod tests { assert!(args.list_groups); assert!(args.command.is_empty()); } + + #[test] + fn test_args_probe_without_command() { + let args = Args::try_parse_from(["proxychains4", "--probe"]); + assert!(args.is_ok()); + let args = args.unwrap(); + assert!(args.probe); + assert!(args.command.is_empty()); + } }