diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a9d686..de2349e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: run: | sudo add-apt-repository ppa:artyom.h31/3proxy -y sudo apt-get update - sudo apt-get install 3proxy -y + sudo apt-get install 3proxy socat -y - name: Run tests run: | cargo build --verbose --all diff --git a/Cargo.toml b/Cargo.toml index 4d31b4f..4280948 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,6 @@ either = "1" thiserror = "1.0" [dev-dependencies] -tokio = { version = "0.2", features = ["io-util", "rt-threaded"] } +tokio = { version = "0.2", features = ["io-util", "rt-threaded", "uds", "dns"] } once_cell = "1.2.0" hyper = "0.13" diff --git a/examples/socket.rs b/examples/socket.rs new file mode 100644 index 0000000..ad8189d --- /dev/null +++ b/examples/socket.rs @@ -0,0 +1,44 @@ +//! Test the tor proxy capabilities +//! +//! This example requires a running tor proxy. + +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpStream, UnixStream}, + runtime::Runtime, +}; +use tokio_socks::{tcp::Socks5Stream, Error}; + +const UNIX_PROXY_ADDR: &str = "/tmp/tor/socket.s"; +const TCP_PROXY_ADDR: &str = "127.0.0.1:9050"; +const ONION_ADDR: &str = "3g2upl4pq6kufc4m.onion:80"; // DuckDuckGo + +async fn connect() -> Result<(), Error> { + // This require Tor to listen on and Unix Domain Socket. + // You have to create a directory /tmp/tor owned by tor, and for which only tor + // has rights, and add the following line to your torrc : + // SocksPort unix:/tmp/tor/socket.s + let socket = UnixStream::connect(UNIX_PROXY_ADDR).await?; + let target = Socks5Stream::tor_resolve_with_socket(socket, "duckduckgo.com:0").await?; + eprintln!("duckduckgo.com = {:?}", target); + let socket = UnixStream::connect(UNIX_PROXY_ADDR).await?; + let target = Socks5Stream::tor_resolve_ptr_with_socket(socket, "176.34.155.23:0").await?; + eprintln!("176.34.155.23 = {:?}", target); + + let socket = TcpStream::connect(TCP_PROXY_ADDR).await?; + socket.set_nodelay(true)?; + let mut conn = Socks5Stream::connect_with_socket(socket, ONION_ADDR).await?; + conn.write_all(b"GET /\n\n").await?; + + let mut buf = Vec::new(); + let n = conn.read_to_end(&mut buf).await?; + + println!("{} bytes read\n\n{}", n, String::from_utf8_lossy(&buf)); + + Ok(()) +} + +fn main() { + let mut rt = Runtime::new().unwrap(); + rt.block_on(connect()).unwrap(); +} diff --git a/src/error.rs b/src/error.rs index 18e293d..1425625 100644 --- a/src/error.rs +++ b/src/error.rs @@ -64,6 +64,5 @@ pub enum Error { PasswordAuthFailure(u8), } - ///// Result type of `tokio-socks` // pub type Result = std::result::Result; diff --git a/src/lib.rs b/src/lib.rs index 382a368..5311729 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ impl Stream for ProxyAddrsStream { Some(Err(_)) => { let err = self.0.take().unwrap().unwrap_err(); Poll::Ready(Some(Err(err.into()))) - } + }, None => unreachable!(), } } @@ -235,8 +235,7 @@ impl IntoTargetAddr<'static> for (String, u16) { } impl<'a, T> IntoTargetAddr<'a> for &'a T -where - T: IntoTargetAddr<'a> + Copy, +where T: IntoTargetAddr<'a> + Copy { fn into_target_addr(self) -> Result> { (*self).into_target_addr() @@ -299,9 +298,7 @@ mod tests { } fn into_target_addr<'a, T>(t: T) -> Result> - where - T: IntoTargetAddr<'a>, - { + where T: IntoTargetAddr<'a> { t.into_target_addr() } diff --git a/src/tcp.rs b/src/tcp.rs index 7e1dd43..ab0e00e 100644 --- a/src/tcp.rs +++ b/src/tcp.rs @@ -3,7 +3,8 @@ use futures::{ stream, stream::Fuse, task::{Context, Poll}, - Stream, StreamExt, + Stream, + StreamExt, }; use std::{ borrow::Borrow, @@ -12,8 +13,10 @@ use std::{ ops::{Deref, DerefMut}, pin::Pin, }; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use tokio::net::TcpStream; +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + net::TcpStream, +}; #[repr(u8)] #[derive(Clone, Copy)] @@ -30,35 +33,36 @@ enum Command { /// A SOCKS5 client. /// -/// For convenience, it can be dereferenced to `tokio_tcp::TcpStream`. +/// For convenience, it can be dereferenced to it's inner socket. #[derive(Debug)] -pub struct Socks5Stream { - tcp: TcpStream, +pub struct Socks5Stream { + socket: S, target: TargetAddr<'static>, } -impl Deref for Socks5Stream { - type Target = TcpStream; +impl Deref for Socks5Stream { + type Target = S; fn deref(&self) -> &Self::Target { - &self.tcp + &self.socket } } -impl DerefMut for Socks5Stream { +impl DerefMut for Socks5Stream { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.tcp + &mut self.socket } } -impl Socks5Stream { - /// Connects to a target server through a SOCKS5 proxy. +impl Socks5Stream { + /// Connects to a target server through a SOCKS5 proxy given the proxy + /// address. /// /// # Error /// /// It propagates the error that occurs in the conversion from `T` to /// `TargetAddr`. - pub async fn connect<'t, P, T>(proxy: P, target: T) -> Result + pub async fn connect<'t, P, T>(proxy: P, target: T) -> Result> where P: ToProxyAddrs, T: IntoTargetAddr<'t>, @@ -66,8 +70,8 @@ impl Socks5Stream { Self::execute_command(proxy, target, Authentication::None, Command::Connect).await } - /// Connects to a target server through a SOCKS5 proxy using given username - /// and password. + /// Connects to a target server through a SOCKS5 proxy using given username, + /// password and the address of the proxy. /// /// # Error /// @@ -78,7 +82,7 @@ impl Socks5Stream { target: T, username: &'a str, password: &'a str, - ) -> Result + ) -> Result> where P: ToProxyAddrs, T: IntoTargetAddr<'t>, @@ -92,24 +96,9 @@ impl Socks5Stream { .await } - fn validate_auth<'a>(auth: &Authentication<'a>) -> Result<()> { - match auth { - Authentication::Password { username, password } => { - let username_len = username.as_bytes().len(); - if username_len < 1 || username_len > 255 { - Err(Error::InvalidAuthValues("username length should between 1 to 255"))? - } - let password_len = password.as_bytes().len(); - if password_len < 1 || password_len > 255 { - Err(Error::InvalidAuthValues("password length should between 1 to 255"))? - } - } - Authentication::None => {} - } - Ok(()) - } - #[cfg(feature = "tor")] + /// Resolve the domain name to an ip using special Tor Resolve command, by + /// connecting to a Tor compatible proxy given it's address. pub async fn tor_resolve<'t, P, T>(proxy: P, target: T) -> Result> where P: ToProxyAddrs, @@ -121,6 +110,9 @@ impl Socks5Stream { } #[cfg(feature = "tor")] + /// Perform a reverse DNS query on the given ip using special Tor Resolve + /// PTR command, by connecting to a Tor compatible proxy given it's + /// address. pub async fn tor_resolve_ptr<'t, P, T>(proxy: P, target: T) -> Result> where P: ToProxyAddrs, @@ -136,7 +128,7 @@ impl Socks5Stream { target: T, auth: Authentication<'a>, command: Command, - ) -> Result + ) -> Result> where P: ToProxyAddrs, T: IntoTargetAddr<'t>, @@ -149,10 +141,107 @@ impl Socks5Stream { Ok(sock) } +} + +impl Socks5Stream +where S: AsyncRead + AsyncWrite + Unpin +{ + /// Connects to a target server through a SOCKS5 proxy given a socket to it. + /// + /// # Error + /// + /// It propagates the error that occurs in the conversion from `T` to + /// `TargetAddr`. + pub async fn connect_with_socket<'t, T>(socket: S, target: T) -> Result> + where T: IntoTargetAddr<'t> { + Self::execute_command_with_socket(socket, target, Authentication::None, Command::Connect).await + } + + /// Connects to a target server through a SOCKS5 proxy using given username, + /// password and a socket to the proxy + /// + /// # Error + /// + /// It propagates the error that occurs in the conversion from `T` to + /// `TargetAddr`. + pub async fn connect_with_password_and_socket<'a, 't, T>( + socket: S, + target: T, + username: &'a str, + password: &'a str, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + Self::execute_command_with_socket( + socket, + target, + Authentication::Password { username, password }, + Command::Connect, + ) + .await + } + + fn validate_auth<'a>(auth: &Authentication<'a>) -> Result<()> { + match auth { + Authentication::Password { username, password } => { + let username_len = username.as_bytes().len(); + if username_len < 1 || username_len > 255 { + Err(Error::InvalidAuthValues("username length should between 1 to 255"))? + } + let password_len = password.as_bytes().len(); + if password_len < 1 || password_len > 255 { + Err(Error::InvalidAuthValues("password length should between 1 to 255"))? + } + }, + Authentication::None => {}, + } + Ok(()) + } + + #[cfg(feature = "tor")] + /// Resolve the domain name to an ip using special Tor Resolve command, by + /// connecting to a Tor compatible proxy given a socket to it. + pub async fn tor_resolve_with_socket<'t, T>(socket: S, target: T) -> Result> + where T: IntoTargetAddr<'t> { + let sock = Self::execute_command_with_socket(socket, target, Authentication::None, Command::TorResolve).await?; - /// Consumes the `Socks5Stream`, returning the inner `tokio_tcp::TcpStream`. - pub fn into_inner(self) -> TcpStream { - self.tcp + Ok(sock.target_addr().to_owned()) + } + + #[cfg(feature = "tor")] + /// Perform a reverse DNS query on the given ip using special Tor Resolve + /// PTR command, by connecting to a Tor compatible proxy given a socket + /// to it. + pub async fn tor_resolve_ptr_with_socket<'t, T>(socket: S, target: T) -> Result> + where T: IntoTargetAddr<'t> { + let sock = + Self::execute_command_with_socket(socket, target, Authentication::None, Command::TorResolvePtr).await?; + + Ok(sock.target_addr().to_owned()) + } + + async fn execute_command_with_socket<'a, 't, T>( + socket: S, + target: T, + auth: Authentication<'a>, + command: Command, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + Self::validate_auth(&auth)?; + + let sock = SocksConnector::new(auth, command, stream::empty().fuse(), target.into_target_addr()?) + .execute_with_socket(socket) + .await?; + + Ok(sock) + } + + /// Consumes the `Socks5Stream`, returning the inner socket. + pub fn into_inner(self) -> S { + self.socket } /// Returns the target address that the proxy server connects to. @@ -162,7 +251,7 @@ impl Socks5Stream { TargetAddr::Domain(domain, port) => { let domain: &str = domain.borrow(); TargetAddr::Domain(domain.into(), *port) - } + }, } } } @@ -179,8 +268,7 @@ pub struct SocksConnector<'a, 't, S> { } impl<'a, 't, S> SocksConnector<'a, 't, S> -where - S: Stream> + Unpin, +where S: Stream> + Unpin { fn new(auth: Authentication<'a>, command: Command, proxy: Fuse, target: TargetAddr<'t>) -> Self { SocksConnector { @@ -195,21 +283,29 @@ where } /// Connect to the proxy server, authenticate and issue the SOCKS command - pub async fn execute(&mut self) -> Result { + pub async fn execute(&mut self) -> Result> { let next_addr = self.proxy.select_next_some().await?; - let mut tcp = TcpStream::connect(next_addr) + let tcp = TcpStream::connect(next_addr) .await .map_err(|_| Error::ProxyServerUnreachable)?; - self.authenticate(&mut tcp).await?; + self.execute_with_socket(tcp).await + } + + pub async fn execute_with_socket( + &mut self, + mut socket: T, + ) -> Result> + { + self.authenticate(&mut socket).await?; // Send request address that should be proxied self.prepare_send_request(); - tcp.write_all(&self.buf[self.ptr..self.len]).await?; + socket.write_all(&self.buf[self.ptr..self.len]).await?; - let target = self.receive_reply(&mut tcp).await?; + let target = self.receive_reply(&mut socket).await?; - Ok(Socks5Stream { tcp, target }) + Ok(Socks5Stream { socket, target }) } fn prepare_send_method_selection(&mut self) { @@ -219,11 +315,11 @@ where Authentication::None => { self.buf[1..3].copy_from_slice(&[1, 0x00]); self.len = 3; - } + }, Authentication::Password { .. } => { self.buf[1..4].copy_from_slice(&[2, 0x00, 0x02]); self.len = 4; - } + }, } } @@ -264,13 +360,13 @@ where self.buf[4..8].copy_from_slice(&addr.ip().octets()); self.buf[8..10].copy_from_slice(&addr.port().to_be_bytes()); self.len = 10; - } + }, TargetAddr::Ip(SocketAddr::V6(addr)) => { self.buf[3] = 0x04; self.buf[4..20].copy_from_slice(&addr.ip().octets()); self.buf[20..22].copy_from_slice(&addr.port().to_be_bytes()); self.len = 22; - } + }, TargetAddr::Domain(domain, port) => { self.buf[3] = 0x03; let domain = domain.as_bytes(); @@ -279,7 +375,7 @@ where self.buf[5..5 + len].copy_from_slice(domain); self.buf[(5 + len)..(7 + len)].copy_from_slice(&port.to_be_bytes()); self.len = 7 + len; - } + }, } } @@ -288,7 +384,7 @@ where self.len = 4; } - async fn password_authentication_protocol(&mut self, tcp: &mut TcpStream) -> Result<()> { + async fn password_authentication_protocol(&mut self, tcp: &mut T) -> Result<()> { self.prepare_send_password_auth(); tcp.write_all(&self.buf[self.ptr..self.len]).await?; @@ -305,7 +401,7 @@ where Ok(()) } - async fn authenticate(&mut self, tcp: &mut TcpStream) -> Result<()> { + async fn authenticate(&mut self, tcp: &mut T) -> Result<()> { // Write request to connect/authenticate self.prepare_send_method_selection(); tcp.write_all(&self.buf[self.ptr..self.len]).await?; @@ -319,13 +415,13 @@ where match self.buf[1] { 0x00 => { // No auth - } + }, 0x02 => { self.password_authentication_protocol(tcp).await?; - } + }, 0xff => { return Err(Error::NoAcceptableAuthMethods); - } + }, m if m != self.auth.id() => return Err(Error::UnknownAuthMethod), _ => unimplemented!(), } @@ -333,7 +429,7 @@ where Ok(()) } - async fn receive_reply(&mut self, tcp: &mut TcpStream) -> Result> { + async fn receive_reply(&mut self, tcp: &mut T) -> Result> { self.prepare_recv_reply(); self.ptr += tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?; if self.buf[0] != 0x05 { @@ -344,7 +440,7 @@ where } match self.buf[1] { - 0x00 => {} // succeeded + 0x00 => {}, // succeeded 0x01 => Err(Error::GeneralSocksServerFailure)?, 0x02 => Err(Error::ConnectionNotAllowedByRuleset)?, 0x03 => Err(Error::NetworkUnreachable)?, @@ -360,17 +456,17 @@ where // IPv4 0x01 => { self.len = 10; - } + }, // IPv6 0x04 => { self.len = 22; - } + }, // Domain 0x03 => { self.len = 5; self.ptr += tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?; self.len += self.buf[4] as usize + 2; - } + }, _ => Err(Error::UnknownAddressType)?, } @@ -383,7 +479,7 @@ where let ip = Ipv4Addr::from(ip); let port = u16::from_be_bytes([self.buf[8], self.buf[9]]); (ip, port).into_target_addr()? - } + }, // IPv6 0x04 => { let mut ip = [0; 16]; @@ -391,7 +487,7 @@ where let ip = Ipv6Addr::from(ip); let port = u16::from_be_bytes([self.buf[20], self.buf[21]]); (ip, port).into_target_addr()? - } + }, // Domain 0x03 => { let domain_bytes = (&self.buf[5..(self.len - 2)]).to_vec(); @@ -399,7 +495,7 @@ where .map_err(|_| Error::InvalidTargetAddress("not a valid UTF-8 string"))?; let port = u16::from_be_bytes([self.buf[self.len - 2], self.buf[self.len - 1]]); TargetAddr::Domain(domain.into(), port) - } + }, _ => unreachable!(), }; @@ -413,11 +509,11 @@ where /// `bind_addr` to the remote process via the primary connection. Then, call the /// `accept` function and wait for the other end connecting to the rendezvous /// address. -pub struct Socks5Listener { - inner: Socks5Stream, +pub struct Socks5Listener { + inner: Socks5Stream, } -impl Socks5Listener { +impl Socks5Listener { /// Initiates a BIND request to the specified proxy. /// /// The proxy will filter incoming connections based on the value of @@ -427,7 +523,7 @@ impl Socks5Listener { /// /// It propagates the error that occurs in the conversion from `T` to /// `TargetAddr`. - pub async fn bind<'t, P, T>(proxy: P, target: T) -> Result + pub async fn bind<'t, P, T>(proxy: P, target: T) -> Result> where P: ToProxyAddrs, T: IntoTargetAddr<'t>, @@ -450,7 +546,7 @@ impl Socks5Listener { target: T, username: &'a str, password: &'a str, - ) -> Result + ) -> Result> where P: ToProxyAddrs, T: IntoTargetAddr<'t>, @@ -458,7 +554,11 @@ impl Socks5Listener { Self::bind_with_auth(Authentication::Password { username, password }, proxy, target).await } - async fn bind_with_auth<'t, P, T>(auth: Authentication<'_>, proxy: P, target: T) -> Result + async fn bind_with_auth<'t, P, T>( + auth: Authentication<'_>, + proxy: P, + target: T, + ) -> Result> where P: ToProxyAddrs, T: IntoTargetAddr<'t>, @@ -474,6 +574,62 @@ impl Socks5Listener { Ok(Socks5Listener { inner: socket }) } +} + +impl Socks5Listener +where S: AsyncRead + AsyncWrite + Unpin +{ + /// Initiates a BIND request to the specified proxy using the given socket + /// to it. + /// + /// The proxy will filter incoming connections based on the value of + /// `target`. + /// + /// # Error + /// + /// It propagates the error that occurs in the conversion from `T` to + /// `TargetAddr`. + pub async fn bind_with_socket<'t, T>(socket: S, target: T) -> Result> + where T: IntoTargetAddr<'t> { + Self::bind_with_auth_and_socket(Authentication::None, socket, target).await + } + + /// Initiates a BIND request to the specified proxy using given username, + /// password and socket to the proxy. + /// + /// The proxy will filter incoming connections based on the value of + /// `target`. + /// + /// # Error + /// + /// It propagates the error that occurs in the conversion from `T` to + /// `TargetAddr`. + pub async fn bind_with_password_and_socket<'a, 't, T>( + socket: S, + target: T, + username: &'a str, + password: &'a str, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + Self::bind_with_auth_and_socket(Authentication::Password { username, password }, socket, target).await + } + + async fn bind_with_auth_and_socket<'t, T>( + auth: Authentication<'_>, + socket: S, + target: T, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + let socket = SocksConnector::new(auth, Command::Bind, stream::empty().fuse(), target.into_target_addr()?) + .execute_with_socket(socket) + .await?; + + Ok(Socks5Listener { inner: socket }) + } /// Returns the address of the proxy-side TCP listener. /// @@ -488,7 +644,7 @@ impl Socks5Listener { /// /// The value of `bind_addr` should be forwarded to the remote process /// before this method is called. - pub async fn accept(mut self) -> Result { + pub async fn accept(mut self) -> Result> { let mut connector = SocksConnector { auth: Authentication::None, command: Command::Bind, @@ -499,35 +655,39 @@ impl Socks5Listener { len: 0, }; - let target = connector.receive_reply(&mut self.inner.tcp).await?; + let target = connector.receive_reply(&mut self.inner.socket).await?; Ok(Socks5Stream { - tcp: self.inner.tcp, + socket: self.inner.socket, target, }) } } -impl AsyncRead for Socks5Stream { +impl AsyncRead for Socks5Stream +where T: AsyncRead + Unpin +{ unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [std::mem::MaybeUninit]) -> bool { - AsyncRead::prepare_uninitialized_buffer(&self.tcp, buf) + AsyncRead::prepare_uninitialized_buffer(&self.socket, buf) } fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { - AsyncRead::poll_read(Pin::new(&mut self.tcp), cx, buf) + AsyncRead::poll_read(Pin::new(&mut self.socket), cx, buf) } } -impl AsyncWrite for Socks5Stream { +impl AsyncWrite for Socks5Stream +where T: AsyncWrite + Unpin +{ fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - AsyncWrite::poll_write(Pin::new(&mut self.tcp), cx, buf) + AsyncWrite::poll_write(Pin::new(&mut self.socket), cx, buf) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - AsyncWrite::poll_flush(Pin::new(&mut self.tcp), cx) + AsyncWrite::poll_flush(Pin::new(&mut self.socket), cx) } fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - AsyncWrite::poll_shutdown(Pin::new(&mut self.tcp), cx) + AsyncWrite::poll_shutdown(Pin::new(&mut self.socket), cx) } } diff --git a/tests/common.rs b/tests/common.rs index a9952bb..2c2d887 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -6,15 +6,17 @@ use std::{ sync::Mutex, }; use tokio::{ - io::{copy, AsyncReadExt, AsyncWriteExt}, - net::TcpListener, + io::{copy, split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + net::{TcpListener, UnixStream}, runtime::Runtime, }; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, + Error, Result, }; +pub const UNIX_PROXY_ADDR: &'static str = "/tmp/proxy.s"; pub const PROXY_ADDR: &'static str = "127.0.0.1:41080"; pub const ECHO_SERVER_ADDR: &'static str = "localhost:10007"; pub const MSG: &[u8] = b"hello"; @@ -37,24 +39,24 @@ pub async fn echo_server() -> Result<()> { Ok(()) } -pub async fn reply_response(mut socket: Socks5Stream) -> Result<[u8; 5]> { +pub async fn reply_response(mut socket: Socks5Stream) -> Result<[u8; 5]> { socket.write_all(MSG).await?; let mut buf = [0; 5]; socket.read_exact(&mut buf).await?; Ok(buf) } -pub async fn test_connect(socket: Socks5Stream) -> Result<()> { +pub async fn test_connect(socket: Socks5Stream) -> Result<()> { let res = reply_response(socket).await?; assert_eq!(&res[..], MSG); Ok(()) } -pub fn test_bind(listener: Socks5Listener) -> Result<()> { +pub fn test_bind(listener: Socks5Listener) -> Result<()> { let bind_addr = listener.bind_addr().to_owned(); runtime().lock().unwrap().spawn(async move { - let mut stream = listener.accept().await.unwrap(); - let (mut reader, mut writer) = stream.split(); + let stream = listener.accept().await.unwrap(); + let (mut reader, mut writer) = split(stream); copy(&mut reader, &mut writer).await.unwrap(); }); @@ -66,6 +68,10 @@ pub fn test_bind(listener: Socks5Listener) -> Result<()> { Ok(()) } +pub async fn connect_unix() -> Result { + UnixStream::connect(UNIX_PROXY_ADDR).await.map_err(Error::Io) +} + pub fn runtime() -> &'static Mutex { static RUNTIME: OnceCell> = OnceCell::new(); RUNTIME.get_or_init(|| { diff --git a/tests/integration_tests.sh b/tests/integration_tests.sh index 90f53a1..c134ae4 100755 --- a/tests/integration_tests.sh +++ b/tests/integration_tests.sh @@ -2,6 +2,9 @@ set -x dir="$(dirname "$(which "$0")")" +SOCK="/tmp/proxy.s" +PROXY_HOST="127.0.0.1:41080" + #socat tcp-listen:10007,fork exec:cat & #echo $! > /tmp/socat-test.pid @@ -12,10 +15,12 @@ else list="$@" fi +socat UNIX-LISTEN:${SOCK},reuseaddr,fork TCP:${PROXY_HOST} & + for test in ${list}; do 3proxy ${dir}/${test}.cfg sleep 1 - cargo test --test ${test} + cargo test --test ${test} -- --test-threads 1 test_exit_code=$? pkill -F /tmp/3proxy-test.pid @@ -27,4 +32,4 @@ done #pkill -F /tmp/socat-test.pid -exit ${test_exit_code} \ No newline at end of file +exit ${test_exit_code} diff --git a/tests/long_username_password_auth.rs b/tests/long_username_password_auth.rs index 7d6f22b..344bf81 100644 --- a/tests/long_username_password_auth.rs +++ b/tests/long_username_password_auth.rs @@ -1,6 +1,6 @@ mod common; -use common::{runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; +use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, Result, @@ -28,3 +28,28 @@ fn bind_long_username_password() -> Result<()> { }?; test_bind(bind) } + +#[test] +fn connect_with_socket_long_username_password() -> Result<()> { + let mut runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix())?; + let conn = runtime.block_on(Socks5Stream::connect_with_password_and_socket( + socket, ECHO_SERVER_ADDR, "mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin", + "longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword"))?; + runtime.block_on(test_connect(conn)) +} + +#[test] +fn bind_with_socket_long_username_password() -> Result<()> { + let bind = { + let mut runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix())?; + runtime.block_on(Socks5Listener::bind_with_password_and_socket( + socket, + ECHO_SERVER_ADDR, + "mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin", + "longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword" + )) + }?; + test_bind(bind) +} diff --git a/tests/no_auth.rs b/tests/no_auth.rs index ad8065f..c0fc581 100644 --- a/tests/no_auth.rs +++ b/tests/no_auth.rs @@ -1,7 +1,7 @@ mod common; use crate::common::{runtime, test_bind}; -use common::{test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; +use common::{connect_unix, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, Result, @@ -22,3 +22,21 @@ fn bind_no_auth() -> Result<()> { }?; test_bind(bind) } + +#[test] +fn connect_with_socket_no_auth() -> Result<()> { + let mut runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix())?; + let conn = runtime.block_on(Socks5Stream::connect_with_socket(socket, ECHO_SERVER_ADDR))?; + runtime.block_on(test_connect(conn)) +} + +#[test] +fn bind_with_socket_no_auth() -> Result<()> { + let bind = { + let mut runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix())?; + runtime.block_on(Socks5Listener::bind_with_socket(socket, ECHO_SERVER_ADDR)) + }?; + test_bind(bind) +} diff --git a/tests/username_auth.rs b/tests/username_auth.rs index 23b7bf1..3694772 100644 --- a/tests/username_auth.rs +++ b/tests/username_auth.rs @@ -1,6 +1,6 @@ mod common; -use common::{runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; +use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, Result, @@ -31,3 +31,31 @@ fn bind_username_auth() -> Result<()> { }?; test_bind(bind) } + +#[test] +fn connect_with_socket_username_auth() -> Result<()> { + let mut runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix())?; + let conn = runtime.block_on(Socks5Stream::connect_with_password_and_socket( + socket, + ECHO_SERVER_ADDR, + "mylogin", + "mypassword", + ))?; + runtime.block_on(test_connect(conn)) +} + +#[test] +fn bind_with_socket_username_auth() -> Result<()> { + let bind = { + let mut runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix())?; + runtime.block_on(Socks5Listener::bind_with_password_and_socket( + socket, + ECHO_SERVER_ADDR, + "mylogin", + "mypassword", + )) + }?; + test_bind(bind) +}