Wire up, and add a test for, connecting via a client through a proxy server.

This commit is contained in:
2021-11-07 14:27:46 -08:00
parent abff1a4ec1
commit c05b0f2b74
2 changed files with 122 additions and 11 deletions

View File

@@ -1,10 +1,15 @@
use crate::errors::{DeserializationError, SerializationError}; use crate::errors::{DeserializationError, SerializationError};
use crate::messages::{ use crate::messages::{
AuthenticationMethod, ClientGreeting, ClientUsernamePassword, ServerAuthResponse, ServerChoice, AuthenticationMethod, ClientConnectionCommand, ClientConnectionRequest, ClientGreeting,
ServerResponseStatus, ClientUsernamePassword, ServerAuthResponse, ServerChoice, ServerResponse, ServerResponseStatus,
}; };
use crate::network::datagram::GenericDatagramSocket;
use crate::network::generic::Networklike; use crate::network::generic::Networklike;
use crate::network::listener::GenericListener;
use crate::network::stream::GenericStream;
use crate::network::SOCKSv5Address;
use async_std::io; use async_std::io;
use async_trait::async_trait;
use futures::io::{AsyncRead, AsyncWrite}; use futures::io::{AsyncRead, AsyncWrite};
use log::{trace, warn}; use log::{trace, warn};
use thiserror::Error; use thiserror::Error;
@@ -27,13 +32,22 @@ pub enum SOCKSv5Error {
ConnectionError(#[from] io::Error), ConnectionError(#[from] io::Error),
} }
impl From<SOCKSv5Error> for ServerResponseStatus {
fn from(x: SOCKSv5Error) -> Self {
match x {
SOCKSv5Error::ServerFailure(v) => v,
_ => ServerResponseStatus::GeneralFailure,
}
}
}
pub struct SOCKSv5Client<S, N> pub struct SOCKSv5Client<S, N>
where where
S: AsyncRead + AsyncWrite, S: AsyncRead + AsyncWrite + Sync,
N: Networklike, N: Networklike + Sync,
{ {
_network: N, network: N,
_stream: S, stream: S,
} }
pub struct LoginInfo { pub struct LoginInfo {
@@ -62,12 +76,12 @@ pub struct UsernamePassword {
impl<S, N> SOCKSv5Client<S, N> impl<S, N> SOCKSv5Client<S, N>
where where
S: AsyncRead + AsyncWrite + Send + Unpin, S: AsyncRead + AsyncWrite + Send + Unpin + Sync,
N: Networklike, N: Networklike + Sync,
{ {
/// Create a new SOCKSv5 client connection over the given steam, using the given /// Create a new SOCKSv5 client connection over the given steam, using the given
/// authentication information. /// authentication information.
pub async fn new(_network: N, mut stream: S, login: &LoginInfo) -> Result<Self, SOCKSv5Error> { pub async fn new(network: N, mut stream: S, login: &LoginInfo) -> Result<Self, SOCKSv5Error> {
let acceptable_methods = login.acceptable_methods(); let acceptable_methods = login.acceptable_methods();
trace!( trace!(
"Computed acceptable methods -- {:?} -- sending client greeting.", "Computed acceptable methods -- {:?} -- sending client greeting.",
@@ -113,8 +127,63 @@ where
trace!("Returning new SOCKSv5Client object!"); trace!("Returning new SOCKSv5Client object!");
Ok(SOCKSv5Client { Ok(SOCKSv5Client {
_network, network,
_stream: stream, stream,
}) })
} }
} }
#[async_trait]
impl<S, N> Networklike for SOCKSv5Client<S, N>
where
S: AsyncRead + AsyncWrite + Send + Unpin + Sync,
N: Networklike + Sync + Send,
{
type Error = SOCKSv5Error;
async fn connect<A: Send + Into<SOCKSv5Address>>(
&mut self,
addr: A,
port: u16,
) -> Result<GenericStream, Self::Error> {
let request = ClientConnectionRequest {
command_code: ClientConnectionCommand::EstablishTCPStream,
destination_address: addr.into(),
destination_port: port,
};
request.write(&mut self.stream).await?;
let response = ServerResponse::read(&mut self.stream).await?;
if response.status == ServerResponseStatus::RequestGranted {
self.network
.connect(response.bound_address, response.bound_port)
.await
.map_err(|e| {
SOCKSv5Error::ConnectionError(io::Error::new(
io::ErrorKind::Other,
format!("{}", e),
))
})
} else {
Err(SOCKSv5Error::ServerFailure(response.status))
}
}
async fn listen<A: Send + Into<SOCKSv5Address>>(
&mut self,
_addr: A,
_port: u16,
) -> Result<GenericListener<Self::Error>, Self::Error> {
unimplemented!()
}
async fn bind<A: Send + Into<SOCKSv5Address>>(
&mut self,
_addr: A,
_port: u16,
) -> Result<GenericDatagramSocket<Self::Error>, Self::Error> {
unimplemented!()
}
}

View File

@@ -9,9 +9,12 @@ pub mod server;
mod test { mod test {
use crate::client::{LoginInfo, SOCKSv5Client, UsernamePassword}; use crate::client::{LoginInfo, SOCKSv5Client, UsernamePassword};
use crate::network::generic::Networklike; use crate::network::generic::Networklike;
use crate::network::listener::Listenerlike;
use crate::network::testing::TestingStack; use crate::network::testing::TestingStack;
use crate::server::{SOCKSv5Server, SecurityParameters}; use crate::server::{SOCKSv5Server, SecurityParameters};
use async_std::io::prelude::WriteExt;
use async_std::task; use async_std::task;
use futures::AsyncReadExt;
#[test] #[test]
fn unrestricted_login() { fn unrestricted_login() {
@@ -127,4 +130,43 @@ mod test {
assert!(client.is_err()); assert!(client.is_err());
}) })
} }
#[test]
fn establish_stream() {
task::block_on(async {
let mut network_stack = TestingStack::default();
let target_port = network_stack.listen("localhost", 1337).await.unwrap();
// generate the server
let security_parameters = SecurityParameters::unrestricted();
let default_port = network_stack.listen("localhost", 9999).await.unwrap();
let server =
SOCKSv5Server::new(network_stack.clone(), security_parameters, default_port);
let _server_task = task::spawn(async move { server.run().await });
let stream = network_stack.connect("localhost", 9999).await.unwrap();
let login_info = LoginInfo {
username_password: None,
};
let mut client = SOCKSv5Client::new(network_stack, stream, &login_info)
.await
.unwrap();
task::spawn(async move {
let mut conn = client.connect("localhost", 1337).await.unwrap();
conn.write_all(&[1, 3, 3, 7, 9]).await.unwrap();
});
let (mut target_connection, _, _) = target_port.accept().await.unwrap();
let mut read_buffer = [0; 4];
target_connection
.read_exact(&mut read_buffer)
.await
.unwrap();
assert_eq!(read_buffer, [1, 3, 3, 7]);
})
}
} }