From 4bda88a15127337f3f1f0d430208be0a584e9794 Mon Sep 17 00:00:00 2001 From: Rohit Kulkarni Date: Sun, 16 Jun 2024 23:18:36 +0530 Subject: [PATCH] Socks4 Support with userId, connect and bind --- .gitignore | 5 +- rustfmt.toml | 2 +- src/error.rs | 6 + src/lib.rs | 8 +- src/tcp.rs | 36 +- src/tcp_socks4.rs | 519 +++++++++++++++++++++++++++ tests/common.rs | 31 +- tests/integration_tests.sh | 9 +- tests/long_username_password_auth.rs | 6 +- tests/no_auth.rs | 6 +- tests/socks4_no_auth.cfg | 4 + tests/socks4_no_auth.rs | 45 +++ tests/socks4_userid.cfg | 6 + tests/socks4_userid.rs | 56 +++ tests/username_auth.rs | 6 +- 15 files changed, 712 insertions(+), 33 deletions(-) create mode 100644 src/tcp_socks4.rs create mode 100644 tests/socks4_no_auth.cfg create mode 100644 tests/socks4_no_auth.rs create mode 100644 tests/socks4_userid.cfg create mode 100644 tests/socks4_userid.rs diff --git a/.gitignore b/.gitignore index be6bbbd..c3c78e1 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,7 @@ Cargo.lock out/ # Vscode files -.vscode/** \ No newline at end of file +.vscode/** + +docker-compose.yml +Dockerfile \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 4d50d9a..548504a 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -use_small_heuristics = "default" +use_small_heuristics = "Default" hard_tabs = false imports_layout = "HorizontalVertical" merge_imports = true diff --git a/src/error.rs b/src/error.rs index ddf34b1..77ca897 100644 --- a/src/error.rs +++ b/src/error.rs @@ -65,6 +65,12 @@ pub enum Error { #[error("Authorization required")] AuthorizationRequired, + + #[error("Request rejected because SOCKS server cannot connect to identd on the client")] + IdentdAuthFailure, + + #[error("Request rejected because the client program and identd report different user-ids")] + InvalidUserIdAuthFailure, } ///// Result type of `tokio-socks` diff --git a/src/lib.rs b/src/lib.rs index e82fa21..34d7704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,7 +235,8 @@ 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() @@ -260,6 +261,7 @@ impl<'a> Authentication<'a> { mod error; pub mod tcp; +pub mod tcp_socks4; #[cfg(test)] mod tests { @@ -299,7 +301,9 @@ 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 c57bfda..a0163b9 100644 --- a/src/tcp.rs +++ b/src/tcp.rs @@ -139,7 +139,8 @@ impl Socks5Stream { } impl Socks5Stream -where S: AsyncRead + AsyncWrite + Unpin +where + S: AsyncRead + AsyncWrite + Unpin, { /// Connects to a target server through a SOCKS5 proxy given a socket to it. /// @@ -148,7 +149,9 @@ where S: AsyncRead + AsyncWrite + Unpin /// 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> { + where + T: IntoTargetAddr<'t>, + { Self::execute_command_with_socket(socket, target, Authentication::None, Command::Connect).await } @@ -198,7 +201,9 @@ where S: AsyncRead + AsyncWrite + Unpin /// 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> { + where + T: IntoTargetAddr<'t>, + { let sock = Self::execute_command_with_socket(socket, target, Authentication::None, Command::TorResolve).await?; Ok(sock.target_addr().to_owned()) @@ -209,7 +214,9 @@ where S: AsyncRead + AsyncWrite + Unpin /// 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> { + where + T: IntoTargetAddr<'t>, + { let sock = Self::execute_command_with_socket(socket, target, Authentication::None, Command::TorResolvePtr).await?; @@ -263,7 +270,8 @@ 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 { @@ -290,8 +298,7 @@ where S: Stream> + Unpin pub async fn execute_with_socket( &mut self, mut socket: T, - ) -> Result> - { + ) -> Result> { self.authenticate(&mut socket).await?; // Send request address that should be proxied @@ -334,7 +341,7 @@ where S: Stream> + Unpin let password_bytes = password.as_bytes(); let password_len = password_bytes.len(); self.len = 3 + username_len + password_len; - self.buf[(2 + username_len)] = password_len as u8; + self.buf[2 + username_len] = password_len as u8; self.buf[(3 + username_len)..self.len].copy_from_slice(password_bytes); } else { unreachable!() @@ -576,7 +583,8 @@ impl Socks5Listener { } impl Socks5Listener -where S: AsyncRead + AsyncWrite + Unpin +where + S: AsyncRead + AsyncWrite + Unpin, { /// Initiates a BIND request to the specified proxy using the given socket /// to it. @@ -589,7 +597,9 @@ where S: AsyncRead + AsyncWrite + Unpin /// 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> { + where + T: IntoTargetAddr<'t>, + { Self::bind_with_auth_and_socket(Authentication::None, socket, target).await } @@ -664,7 +674,8 @@ where S: AsyncRead + AsyncWrite + Unpin } impl AsyncRead for Socks5Stream -where T: AsyncRead + Unpin +where + T: AsyncRead + Unpin, { fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { AsyncRead::poll_read(Pin::new(&mut self.socket), cx, buf) @@ -672,7 +683,8 @@ where T: AsyncRead + Unpin } impl AsyncWrite for Socks5Stream -where T: AsyncWrite + Unpin +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.socket), cx, buf) diff --git a/src/tcp_socks4.rs b/src/tcp_socks4.rs new file mode 100644 index 0000000..ab27001 --- /dev/null +++ b/src/tcp_socks4.rs @@ -0,0 +1,519 @@ +use crate::{Error, IntoTargetAddr, Result, TargetAddr, ToProxyAddrs}; +use futures_util::stream::{self, Fuse, Stream, StreamExt}; +use std::{ + borrow::Borrow, + io, + net::{Ipv4Addr, SocketAddr}, + ops::{Deref, DerefMut}, + pin::Pin, + task::{Context, Poll}, +}; +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}, + net::TcpStream, +}; + +#[repr(u8)] +#[derive(Clone, Copy)] +enum CommandV4 { + Connect = 0x01, + Bind = 0x02, +} + +/// A SOCKS4 client. +/// +/// For convenience, it can be dereferenced to it's inner socket. +#[derive(Debug)] +pub struct Socks4Stream { + socket: S, + target: TargetAddr<'static>, +} + +impl Deref for Socks4Stream { + type Target = S; + + fn deref(&self) -> &Self::Target { + &self.socket + } +} + +impl DerefMut for Socks4Stream { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.socket + } +} + +impl Socks4Stream { + /// Connects to a target server through a SOCKS4 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> + where + P: ToProxyAddrs, + T: IntoTargetAddr<'t>, + { + Self::execute_command(proxy, target, None, CommandV4::Connect).await + } + + /// Connects to a target server through a SOCKS5 proxy using given username, + /// password and the address of the proxy. + /// + /// # Error + /// + /// It propagates the error that occurs in the conversion from `T` to + /// `TargetAddr`. + pub async fn connect_with_userid<'a, 't, P, T>( + proxy: P, + target: T, + user_id: &'a str, + ) -> Result> + where + P: ToProxyAddrs, + T: IntoTargetAddr<'t>, + { + Self::execute_command(proxy, target, Some(user_id), CommandV4::Connect).await + } + + async fn execute_command<'a, 't, P, T>( + proxy: P, + target: T, + user_id: Option<&'a str>, + command: CommandV4, + ) -> Result> + where + P: ToProxyAddrs, + T: IntoTargetAddr<'t>, + { + Self::validate_userid(user_id)?; + + let sock = Socks4Connector::new( + user_id, + command, + proxy.to_proxy_addrs().fuse(), + target.into_target_addr()?, + ) + .execute() + .await?; + + Ok(sock) + } +} + +impl Socks4Stream +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, None, CommandV4::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_userid_and_socket<'a, 't, T>( + socket: S, + target: T, + user_id: &'a str, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + Self::execute_command_with_socket(socket, target, Some(user_id), CommandV4::Connect).await + } + + fn validate_userid<'a>(user_id: Option<&'a str>) -> Result<()> { + /* + A hardcode limit for length of userid must be enforced to avoid, buffer overflow. + */ + if let Some(user_id) = user_id { + let user_id_len = user_id.len(); + if user_id_len < 1 || user_id_len > 255 { + Err(Error::InvalidAuthValues("userid length should between 1 to 255"))? + } + } + + Ok(()) + } + + async fn execute_command_with_socket<'a, 't, T>( + socket: S, + target: T, + user_id: Option<&'a str>, + command: CommandV4, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + Self::validate_userid(user_id)?; + + let sock = Socks4Connector::new(user_id, command, stream::empty().fuse(), target.into_target_addr()?) + .execute_with_socket(socket) + .await?; + + Ok(sock) + } + + /// Consumes the `Socks4Stream`, returning the inner socket. + pub fn into_inner(self) -> S { + self.socket + } + + /// Returns the target address that the proxy server connects to. + pub fn target_addr(&self) -> TargetAddr<'_> { + match &self.target { + TargetAddr::Ip(addr) => TargetAddr::Ip(*addr), + TargetAddr::Domain(domain, port) => { + let domain: &str = domain.borrow(); + TargetAddr::Domain(domain.into(), *port) + }, + } + } +} + +/// A `Future` which resolves to a socket to the target server through proxy. +pub struct Socks4Connector<'a, 't, S> { + user_id: Option<&'a str>, + command: CommandV4, + proxy: Fuse, + target: TargetAddr<'t>, + buf: [u8; 513], + ptr: usize, + len: usize, +} + +impl<'a, 't, S> Socks4Connector<'a, 't, S> +where + S: Stream> + Unpin, +{ + fn new(user_id: Option<&'a str>, command: CommandV4, proxy: Fuse, target: TargetAddr<'t>) -> Self { + Socks4Connector { + user_id, + command, + proxy, + target, + buf: [0; 513], + ptr: 0, + len: 0, + } + } + + /// Connect to the proxy server, authenticate and issue the SOCKS command + pub async fn execute(&mut self) -> Result> { + let next_addr = self.proxy.select_next_some().await?; + let tcp = TcpStream::connect(next_addr) + .await + .map_err(|_| Error::ProxyServerUnreachable)?; + + self.execute_with_socket(tcp).await + } + + pub async fn execute_with_socket( + &mut self, + mut socket: T, + ) -> Result> { + // Send request address that should be proxied + self.prepare_send_request()?; + socket.write_all(&self.buf[self.ptr..self.len]).await?; + + let target = self.receive_reply(&mut socket).await?; + + Ok(Socks4Stream { socket, target }) + } + + fn prepare_send_request(&mut self) -> Result<()> { + self.ptr = 0; + self.buf[..2].copy_from_slice(&[0x04, self.command as u8]); + match &self.target { + TargetAddr::Ip(SocketAddr::V4(addr)) => { + self.buf[2..4].copy_from_slice(&addr.port().to_be_bytes()); + self.buf[4..8].copy_from_slice(&addr.ip().octets()); + self.len = 8; + if let Some(user_id) = self.user_id { + let usr_byts = user_id.as_bytes(); + let user_id_len = usr_byts.len(); + self.len += user_id_len; + self.buf[8..self.len].copy_from_slice(usr_byts); + } + self.buf[self.len] = 0; // null terminator + self.len += 1; + }, + TargetAddr::Ip(SocketAddr::V6(_)) => { + return Err(Error::AddressTypeNotSupported); + }, + TargetAddr::Domain(domain, port) => { + self.buf[2..4].copy_from_slice(&port.to_be_bytes()); + self.buf[4..8].copy_from_slice(&[0, 0, 0, 1]); + self.len = 8; + if let Some(user_id) = self.user_id { + let usr_byts = user_id.as_bytes(); + let user_id_len = usr_byts.len(); + self.len += user_id_len; + self.buf[8..self.len].copy_from_slice(usr_byts); + } + self.buf[self.len] = 0; // null terminator + self.len += 1; + let domain = domain.as_bytes(); + let domain_len = domain.len(); + self.buf[self.len..self.len + domain_len].copy_from_slice(domain); + self.len += domain_len; + self.buf[self.len] = 0; + self.len += 1; + }, + }; + Ok(()) + } + + fn prepare_recv_reply(&mut self) { + self.ptr = 0; + self.len = 8; + } + + async fn receive_reply(&mut self, tcp: &mut T) -> Result> { + /* + https://www.openssh.com/txt/socks4.protocol + +----+----+----+----+----+----+----+----+ + | VN | CD | DSTPORT | DSTIP | + +----+----+----+----+----+----+----+----+ + # of bytes: 1 1 2 4 + + VN is the version of the reply code and should be 0. CD is the result + code with one of the following values: + 90: request granted + 91: request rejected or failed + 92: request rejected becasue SOCKS server cannot connect to + identd on the client + 93: request rejected because the client program and identd + report different user-ids + */ + + self.prepare_recv_reply(); + self.ptr += tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?; + if self.buf[0] != 0 { + return Err(Error::InvalidResponseVersion); + } + + match self.buf[1] { + 0x5A => {}, // request granted + 0x5B => return Err(Error::GeneralSocksServerFailure), // connection rejected/failed + 0x5C => return Err(Error::IdentdAuthFailure), // cannot connect to identd on the client + 0x5D => return Err(Error::InvalidUserIdAuthFailure), // different user-ids + _ => return Err(Error::UnknownError), + } + + let port = u16::from_be_bytes([self.buf[2], self.buf[3]]); + + let target = Ipv4Addr::from([self.buf[4], self.buf[5], self.buf[6], self.buf[7]]); + + Ok(TargetAddr::Ip(SocketAddr::new(target.into(), port))) + } +} + +/// A SOCKS5 BIND client. +/// +/// Once you get an instance of `Socks4Listener`, you should send the +/// `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 Socks4Listener { + inner: Socks4Stream, +} + +impl Socks4Listener { + /// Initiates a BIND request to the specified 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<'t, P, T>(proxy: P, target: T) -> Result> + where + P: ToProxyAddrs, + T: IntoTargetAddr<'t>, + { + Self::bind_to_target(None, proxy, target).await + } + + /// Initiates a BIND request to the specified proxy using given username + /// and password. + /// + /// 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_userid<'a, 't, P, T>( + proxy: P, + target: T, + user_id: &'a str, + ) -> Result> + where + P: ToProxyAddrs, + T: IntoTargetAddr<'t>, + { + Self::bind_to_target(Some(user_id), proxy, target).await + } + + async fn bind_to_target<'a, 't, P, T>( + user_id: Option<&'a str>, + proxy: P, + target: T, + ) -> Result> + where + P: ToProxyAddrs, + T: IntoTargetAddr<'t>, + { + let socket = Socks4Connector::new( + user_id, + CommandV4::Bind, + proxy.to_proxy_addrs().fuse(), + target.into_target_addr()?, + ) + .execute() + .await?; + + Ok(Socks4Listener { inner: socket }) + } +} + +impl Socks4Listener +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_to_target_with_socket(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_user_and_socket<'a, 't, T>( + socket: S, + target: T, + user_id: &'a str, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + Self::bind_to_target_with_socket(Some(user_id), socket, target).await + } + + async fn bind_to_target_with_socket<'a, 't, T>( + auth: Option<&'a str>, + socket: S, + target: T, + ) -> Result> + where + T: IntoTargetAddr<'t>, + { + let socket = Socks4Connector::new( + auth, + CommandV4::Bind, + stream::empty().fuse(), + target.into_target_addr()?, + ) + .execute_with_socket(socket) + .await?; + + Ok(Socks4Listener { inner: socket }) + } + + /// Returns the address of the proxy-side TCP listener. + /// + /// This should be forwarded to the remote process, which should open a + /// connection to it. + pub fn bind_addr(&self) -> TargetAddr { + self.inner.target_addr() + } + + /// Consumes this listener, returning a `Future` which resolves to the + /// `Socks4Stream` connected to the target server through the proxy. + /// + /// The value of `bind_addr` should be forwarded to the remote process + /// before this method is called. + pub async fn accept(mut self) -> Result> { + let mut connector = Socks4Connector { + user_id: None, + command: CommandV4::Bind, + proxy: stream::empty().fuse(), + target: self.inner.target, + buf: [0; 513], + ptr: 0, + len: 0, + }; + + let target = connector.receive_reply(&mut self.inner.socket).await?; + + Ok(Socks4Stream { + socket: self.inner.socket, + target, + }) + } +} + +impl AsyncRead for Socks4Stream +where + T: AsyncRead + Unpin, +{ + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { + AsyncRead::poll_read(Pin::new(&mut self.socket), cx, buf) + } +} + +impl AsyncWrite for Socks4Stream +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.socket), cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + 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.socket), cx) + } +} diff --git a/tests/common.rs b/tests/common.rs index 171a6e1..8b2e141 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,6 +2,7 @@ use once_cell::sync::OnceCell; use std::{ io::{Read, Write}, net::{SocketAddr, TcpStream as StdTcpStream}, + ops::{Deref, DerefMut}, sync::Mutex, }; use tokio::{ @@ -11,12 +12,14 @@ use tokio::{ }; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, - Error, - Result, + tcp_socks4::{Socks4Listener, Socks4Stream}, + 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 UNIX_SOCKS4_PROXY_ADDR: &'static str = "/tmp/socks4_proxy.s"; +pub const SOCKS4_PROXY_ADDR: &'static str = "127.0.0.1:41081"; pub const ECHO_SERVER_ADDR: &'static str = "localhost:10007"; pub const MSG: &[u8] = b"hello"; @@ -31,14 +34,14 @@ pub async fn echo_server() -> Result<()> { } } -pub async fn reply_response(mut socket: Socks5Stream) -> Result<[u8; 5]> { +pub async fn reply_response(mut socket: S) -> 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: S) -> Result<()> { let res = reply_response(socket).await?; assert_eq!(&res[..], MSG); Ok(()) @@ -60,8 +63,8 @@ pub fn test_bind(listener: S Ok(()) } -pub async fn connect_unix() -> Result { - UnixStream::connect(UNIX_PROXY_ADDR).await.map_err(Error::Io) +pub async fn connect_unix(proxy_addr: &'static str) -> Result { + UnixStream::connect(proxy_addr).await.map_err(Error::Io) } pub fn runtime() -> &'static Mutex { @@ -72,3 +75,19 @@ pub fn runtime() -> &'static Mutex { Mutex::new(runtime) }) } + +pub fn test_bind_socks4(listener: Socks4Listener) -> Result<()> { + let bind_addr = listener.bind_addr().to_owned(); + runtime().lock().unwrap().spawn(async move { + let stream = listener.accept().await.unwrap(); + let (mut reader, mut writer) = split(stream); + copy(&mut reader, &mut writer).await.unwrap(); + }); + + let mut tcp = StdTcpStream::connect(bind_addr)?; + tcp.write_all(MSG)?; + let mut buf = [0; 5]; + tcp.read_exact(&mut buf[..])?; + assert_eq!(&buf[..], MSG); + Ok(()) +} diff --git a/tests/integration_tests.sh b/tests/integration_tests.sh index c134ae4..100182e 100755 --- a/tests/integration_tests.sh +++ b/tests/integration_tests.sh @@ -4,18 +4,22 @@ set -x dir="$(dirname "$(which "$0")")" SOCK="/tmp/proxy.s" PROXY_HOST="127.0.0.1:41080" +SOCK4="/tmp/socks4_proxy.s" +SOCKS4_PROXY_HOST="127.0.0.1:41081" #socat tcp-listen:10007,fork exec:cat & #echo $! > /tmp/socat-test.pid if test -z "$@"; then - list="no_auth username_auth long_username_password_auth" + list="socks4_no_auth socks4_userid no_auth username_auth long_username_password_auth" else list="$@" fi socat UNIX-LISTEN:${SOCK},reuseaddr,fork TCP:${PROXY_HOST} & +socat UNIX-LISTEN:${SOCK4},reuseaddr,fork TCP:${SOCKS4_PROXY_HOST} & +sleep 2 for test in ${list}; do 3proxy ${dir}/${test}.cfg @@ -24,6 +28,7 @@ for test in ${list}; do test_exit_code=$? pkill -F /tmp/3proxy-test.pid + sleep 1 if test "$test_exit_code" -ne 0; then break @@ -31,5 +36,5 @@ for test in ${list}; do done -#pkill -F /tmp/socat-test.pid +# pkill -F /tmp/socat-test.pid exit ${test_exit_code} diff --git a/tests/long_username_password_auth.rs b/tests/long_username_password_auth.rs index ec83e95..f9bc664 100644 --- a/tests/long_username_password_auth.rs +++ b/tests/long_username_password_auth.rs @@ -1,6 +1,6 @@ mod common; -use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; +use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR, UNIX_PROXY_ADDR}; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, Result, @@ -32,7 +32,7 @@ fn bind_long_username_password() -> Result<()> { #[test] fn connect_with_socket_long_username_password() -> Result<()> { let runtime = runtime().lock().unwrap(); - let socket = runtime.block_on(connect_unix())?; + let socket = runtime.block_on(connect_unix(UNIX_PROXY_ADDR))?; let conn = runtime.block_on(Socks5Stream::connect_with_password_and_socket( socket, ECHO_SERVER_ADDR, "mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin", "longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword"))?; @@ -43,7 +43,7 @@ fn connect_with_socket_long_username_password() -> Result<()> { fn bind_with_socket_long_username_password() -> Result<()> { let bind = { let runtime = runtime().lock().unwrap(); - let socket = runtime.block_on(connect_unix())?; + let socket = runtime.block_on(connect_unix(UNIX_PROXY_ADDR))?; runtime.block_on(Socks5Listener::bind_with_password_and_socket( socket, ECHO_SERVER_ADDR, diff --git a/tests/no_auth.rs b/tests/no_auth.rs index ceca550..a44560a 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::{connect_unix, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; +use common::{connect_unix, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR, UNIX_PROXY_ADDR}; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, Result, @@ -26,7 +26,7 @@ fn bind_no_auth() -> Result<()> { #[test] fn connect_with_socket_no_auth() -> Result<()> { let runtime = runtime().lock().unwrap(); - let socket = runtime.block_on(connect_unix())?; + let socket = runtime.block_on(connect_unix(UNIX_PROXY_ADDR))?; let conn = runtime.block_on(Socks5Stream::connect_with_socket(socket, ECHO_SERVER_ADDR))?; runtime.block_on(test_connect(conn)) } @@ -35,7 +35,7 @@ fn connect_with_socket_no_auth() -> Result<()> { fn bind_with_socket_no_auth() -> Result<()> { let bind = { let runtime = runtime().lock().unwrap(); - let socket = runtime.block_on(connect_unix())?; + let socket = runtime.block_on(connect_unix(UNIX_PROXY_ADDR))?; runtime.block_on(Socks5Listener::bind_with_socket(socket, ECHO_SERVER_ADDR)) }?; test_bind(bind) diff --git a/tests/socks4_no_auth.cfg b/tests/socks4_no_auth.cfg new file mode 100644 index 0000000..98e7d72 --- /dev/null +++ b/tests/socks4_no_auth.cfg @@ -0,0 +1,4 @@ +daemon +pidfile /tmp/3proxy-test.pid +auth none +socks -p41081 -4 \ No newline at end of file diff --git a/tests/socks4_no_auth.rs b/tests/socks4_no_auth.rs new file mode 100644 index 0000000..8b03774 --- /dev/null +++ b/tests/socks4_no_auth.rs @@ -0,0 +1,45 @@ +mod common; + +use crate::common::runtime; +use common::{ + connect_unix, test_bind_socks4, test_connect, ECHO_SERVER_ADDR, SOCKS4_PROXY_ADDR, UNIX_SOCKS4_PROXY_ADDR, +}; +use tokio_socks::{ + tcp_socks4::{Socks4Listener, Socks4Stream}, + Result, +}; + +#[test] +fn connect_no_auth() -> Result<()> { + let runtime = runtime().lock().unwrap(); + let conn = runtime.block_on(Socks4Stream::connect(SOCKS4_PROXY_ADDR, ECHO_SERVER_ADDR))?; + runtime.block_on(test_connect(conn)) +} + +#[test] +fn bind_no_auth() -> Result<()> { + let bind = { + let runtime = runtime().lock().unwrap(); + runtime.block_on(Socks4Listener::bind(SOCKS4_PROXY_ADDR, ECHO_SERVER_ADDR)) + }?; + test_bind_socks4(bind) +} + +#[test] +fn connect_with_socket_no_auth() -> Result<()> { + let runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix(UNIX_SOCKS4_PROXY_ADDR))?; + println!("socket connected"); + let conn = runtime.block_on(Socks4Stream::connect_with_socket(socket, ECHO_SERVER_ADDR))?; + runtime.block_on(test_connect(conn)) +} + +#[test] +fn bind_with_socket_no_auth() -> Result<()> { + let bind = { + let runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix(UNIX_SOCKS4_PROXY_ADDR))?; + runtime.block_on(Socks4Listener::bind_with_socket(socket, ECHO_SERVER_ADDR)) + }?; + test_bind_socks4(bind) +} diff --git a/tests/socks4_userid.cfg b/tests/socks4_userid.cfg new file mode 100644 index 0000000..20751e1 --- /dev/null +++ b/tests/socks4_userid.cfg @@ -0,0 +1,6 @@ +daemon +pidfile /tmp/3proxy-test.pid +users mylogin:CL: +allow mylogin +auth strong +socks -p41081 -4 \ No newline at end of file diff --git a/tests/socks4_userid.rs b/tests/socks4_userid.rs new file mode 100644 index 0000000..1e7d1a6 --- /dev/null +++ b/tests/socks4_userid.rs @@ -0,0 +1,56 @@ +mod common; + +use common::{ + connect_unix, runtime, test_bind_socks4, test_connect, ECHO_SERVER_ADDR, SOCKS4_PROXY_ADDR, UNIX_SOCKS4_PROXY_ADDR, +}; +use tokio_socks::{tcp_socks4::*, Result}; + +#[test] +fn connect_userid() -> Result<()> { + let runtime = runtime().lock().unwrap(); + let conn = runtime.block_on(Socks4Stream::connect_with_userid( + SOCKS4_PROXY_ADDR, + ECHO_SERVER_ADDR, + "mylogin", + ))?; + runtime.block_on(test_connect(conn)) +} + +#[test] +fn bind_userid() -> Result<()> { + let bind = { + let runtime = runtime().lock().unwrap(); + runtime.block_on(Socks4Listener::bind_with_userid( + SOCKS4_PROXY_ADDR, + ECHO_SERVER_ADDR, + "mylogin", + )) + }?; + test_bind_socks4(bind) +} + +#[test] +fn connect_with_socket_userid() -> Result<()> { + let runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix(UNIX_SOCKS4_PROXY_ADDR))?; + let conn = runtime.block_on(Socks4Stream::connect_with_userid_and_socket( + socket, + ECHO_SERVER_ADDR, + "mylogin", + ))?; + runtime.block_on(test_connect(conn)) +} + +#[test] +fn bind_with_socket_userid() -> Result<()> { + let bind = { + let runtime = runtime().lock().unwrap(); + let socket = runtime.block_on(connect_unix(UNIX_SOCKS4_PROXY_ADDR))?; + runtime.block_on(Socks4Listener::bind_with_user_and_socket( + socket, + ECHO_SERVER_ADDR, + "mylogin", + )) + }?; + test_bind_socks4(bind) +} diff --git a/tests/username_auth.rs b/tests/username_auth.rs index 3f53e1f..e042d69 100644 --- a/tests/username_auth.rs +++ b/tests/username_auth.rs @@ -1,6 +1,6 @@ mod common; -use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR}; +use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR, UNIX_PROXY_ADDR}; use tokio_socks::{ tcp::{Socks5Listener, Socks5Stream}, Result, @@ -35,7 +35,7 @@ fn bind_username_auth() -> Result<()> { #[test] fn connect_with_socket_username_auth() -> Result<()> { let runtime = runtime().lock().unwrap(); - let socket = runtime.block_on(connect_unix())?; + let socket = runtime.block_on(connect_unix(UNIX_PROXY_ADDR))?; let conn = runtime.block_on(Socks5Stream::connect_with_password_and_socket( socket, ECHO_SERVER_ADDR, @@ -49,7 +49,7 @@ fn connect_with_socket_username_auth() -> Result<()> { fn bind_with_socket_username_auth() -> Result<()> { let bind = { let runtime = runtime().lock().unwrap(); - let socket = runtime.block_on(connect_unix())?; + let socket = runtime.block_on(connect_unix(UNIX_PROXY_ADDR))?; runtime.block_on(Socks5Listener::bind_with_password_and_socket( socket, ECHO_SERVER_ADDR,