diff --git a/src/proxy.rs b/src/proxy.rs index 36a4086..471bd53 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -4,6 +4,8 @@ use crate::error::{Error, ErrorKind}; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Proto { HTTPConnect, + SOCKS4, + SOCKS4A, SOCKS5, } @@ -68,7 +70,9 @@ impl Proxy { /// * `proxy` - a str of format `://:@:port` . All parts except host are optional. /// # Protocols /// * `http`: HTTP Connect - /// * `socks`, `socks5`: SOCKS5 (requires socks feature) + /// * `socks4`: SOCKS4 (requires socks feature) + /// * `socks4a`: SOCKS4A (requires socks feature) + /// * `socks5` and `socks`: SOCKS5 (requires socks feature) /// # Examples /// * `http://127.0.0.1:8080` /// * `socks5://john:smith@socks.google.com` @@ -84,6 +88,8 @@ impl Proxy { let proto = if proxy_parts.len() == 2 { match proxy_parts.next() { Some("http") => Proto::HTTPConnect, + Some("socks4") => Proto::SOCKS4, + Some("socks4a") => Proto::SOCKS4A, Some("socks") => Proto::SOCKS5, Some("socks5") => Proto::SOCKS5, _ => return Err(ErrorKind::InvalidProxyUrl.new()), @@ -131,7 +137,7 @@ impl Proxy { match self.proto { Proto::HTTPConnect => format!("Proxy-Authorization: basic {}\r\n", creds), - Proto::SOCKS5 => String::new(), + _ => String::new(), } } else { String::new() @@ -192,8 +198,8 @@ mod tests { #[cfg(feature = "socks-proxy")] #[test] - fn parse_proxy_socks_user_pass_server_port() { - let proxy = Proxy::new("socks://user:p@ssw0rd@localhost:9999").unwrap(); + fn parse_proxy_socks4_user_pass_server_port() { + let proxy = Proxy::new("socks4://user:p@ssw0rd@localhost:9999").unwrap(); assert_eq!(proxy.user, Some(String::from("user"))); assert_eq!(proxy.password, Some(String::from("p@ssw0rd"))); assert_eq!(proxy.server, String::from("localhost")); diff --git a/src/stream.rs b/src/stream.rs index 6857dd0..ba13ad8 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -411,14 +411,15 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result Result, proxy_addr: SocketAddr, host: &str, port: u16, + proto: Proto, ) -> Result { use socks::TargetAddr::Domain; use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; - let host_addr = if Ipv4Addr::from_str(host).is_ok() || Ipv6Addr::from_str(host).is_ok() { - match socks5_local_nslookup(unit, host, port) { + let host_addr = if Ipv4Addr::from_str(host).is_ok() + || Ipv6Addr::from_str(host).is_ok() + || proto == Proto::SOCKS4 + { + match socks_local_nslookup(unit, host, port) { Ok(addr) => addr, Err(err) => return Err(err), } @@ -530,14 +535,14 @@ fn connect_socks5( Domain(String::from(host), port) }; - // Since Socks5Stream doesn't support set_read_timeout, a suboptimal one is implemented via + // Since SocksXStream doesn't support set_read_timeout, a suboptimal one is implemented via // thread::spawn. // # Happy Path - // 1) thread spawns 2) get_socks5_stream returns ok 3) tx sends result ok + // 1) thread spawns 2) get_socksX_stream returns ok 3) tx sends result ok // 4) slave_signal signals done and cvar notifies master_signal 5) cvar.wait_timeout receives the done signal // 6) rx receives the socks5 stream and the function exists // # Sad path - // 1) get_socks5_stream hangs 2)slave_signal does not send done notification 3) cvar.wait_timeout times out + // 1) get_socksX_stream hangs 2)slave_signal does not send done notification 3) cvar.wait_timeout times out // 3) an exception is thrown. // # Defects // 1) In the event of a timeout, a thread may be left running in the background. @@ -552,8 +557,12 @@ fn connect_socks5( let (tx, rx) = channel(); thread::spawn(move || { let (lock, cvar) = &*slave_signal; - if tx // try to get a socks5 stream and send it to the parent thread's rx - .send(get_socks5_stream(&proxy, &proxy_addr, host_addr)) + if tx // try to get a socks stream and send it to the parent thread's rx + .send(if proto == Proto::SOCKS5 { + get_socks5_stream(&proxy, &proxy_addr, host_addr) + } else { + get_socks4_stream(&proxy_addr, host_addr) + }) .is_ok() { // if sending the stream has succeeded we need to notify the parent thread @@ -575,14 +584,16 @@ fn connect_socks5( rx.recv().unwrap()? } else { return Err(io_err_timeout(format!( - "SOCKS5 proxy: {}:{} timed out connecting after {}ms.", + "SOCKS proxy: {}:{} timed out connecting after {}ms.", host, port, timeout_connect.as_millis() ))); } - } else { + } else if proto == Proto::SOCKS5 { get_socks5_stream(&proxy, &proxy_addr, host_addr)? + } else { + get_socks4_stream(&proxy_addr, host_addr)? }; Ok(stream) @@ -612,18 +623,30 @@ fn get_socks5_stream( } } +#[cfg(feature = "socks-proxy")] +fn get_socks4_stream( + proxy_addr: &SocketAddr, + host_addr: TargetAddr, +) -> Result { + match socks::Socks4Stream::connect(proxy_addr, host_addr, "") { + Ok(socks_stream) => Ok(socks_stream.into_inner()), + Err(err) => Err(err), + } +} + #[cfg(not(feature = "socks-proxy"))] -fn connect_socks5( +fn connect_socks( _unit: &Unit, _proxy: Proxy, _deadline: Option, _proxy_addr: SocketAddr, _hostname: &str, _port: u16, + _proto: Proto, ) -> Result { Err(std::io::Error::new( io::ErrorKind::Other, - "SOCKS5 feature disabled.", + "SOCKS feature disabled.", )) }