add support for socks4 and socks4a
This commit is contained in:
14
src/proxy.rs
14
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 `<protocol>://<user>:<password>@<host>: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"));
|
||||
|
||||
@@ -411,14 +411,15 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<Tcp
|
||||
|
||||
debug!("connecting to {} at {}", netloc, &sock_addr);
|
||||
// connect with a configured timeout.
|
||||
let stream = if Some(Proto::SOCKS5) == proto {
|
||||
connect_socks5(
|
||||
let stream = if let Some(proto_r) = proto {
|
||||
connect_socks(
|
||||
unit,
|
||||
proxy.clone().unwrap(),
|
||||
connect_deadline,
|
||||
sock_addr,
|
||||
hostname,
|
||||
port,
|
||||
proto_r,
|
||||
)
|
||||
} else if let Some(timeout) = timeout {
|
||||
TcpStream::connect_timeout(&sock_addr, timeout)
|
||||
@@ -478,7 +479,7 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<Tcp
|
||||
}
|
||||
|
||||
#[cfg(feature = "socks-proxy")]
|
||||
fn socks5_local_nslookup(
|
||||
fn socks_local_nslookup(
|
||||
unit: &Unit,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
@@ -509,20 +510,24 @@ fn socks5_local_nslookup(
|
||||
}
|
||||
|
||||
#[cfg(feature = "socks-proxy")]
|
||||
fn connect_socks5(
|
||||
fn connect_socks(
|
||||
unit: &Unit,
|
||||
proxy: Proxy,
|
||||
deadline: Option<Instant>,
|
||||
proxy_addr: SocketAddr,
|
||||
host: &str,
|
||||
port: u16,
|
||||
proto: Proto,
|
||||
) -> Result<TcpStream, std::io::Error> {
|
||||
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<TcpStream, std::io::Error> {
|
||||
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<Instant>,
|
||||
_proxy_addr: SocketAddr,
|
||||
_hostname: &str,
|
||||
_port: u16,
|
||||
_proto: Proto,
|
||||
) -> Result<TcpStream, std::io::Error> {
|
||||
Err(std::io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"SOCKS5 feature disabled.",
|
||||
"SOCKS feature disabled.",
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user