#![doc = include_str!("../SOCKS4.md")]
use rustls::ClientConfig;
use rustls_pki_types::ServerName;
use std::{marker::PhantomData, net::SocketAddrV4, sync::Arc};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
};
use tokio_rustls::{TlsConnector, client::TlsStream};
use crate::errors;
static VERSION_4: u8 = 0x04;
static CONNECT_REQUEST: u8 = 0x01;
static NULL: u8 = 0x00;
#[derive(Debug)]
pub(crate) struct Reply;
impl TryFrom<u8> for Reply {
type Error = errors::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
90 => Ok(Reply),
91 => Err(errors::Error::ProxyResponseNotOk(
"Request rejected or failed.".into(),
)),
92 => Err(errors::Error::ProxyResponseNotOk(
"Request rejected due to inability to connect to identd on the client.".into(),
)),
93 => Err(errors::Error::ProxyResponseNotOk(
"Request rejected because the client program and identd report different user-IDs."
.into(),
)),
_ => Err(errors::Error::ProxyResponseNotOk(
"unknown status reply from proxy server".into(),
)),
}
}
}
pub struct NoProxy;
pub struct HasProxy;
#[derive(Default)]
pub struct Socks4Builder<S = NoProxy> {
phantom: PhantomData<S>,
proxy: Option<SocketAddrV4>,
to: Option<SocketAddrV4>,
}
impl Socks4Builder<NoProxy> {
pub fn new() -> Self {
Self {
phantom: PhantomData,
proxy: None,
to: None,
}
}
pub fn proxy(self, ipv4: SocketAddrV4) -> Socks4Builder<HasProxy> {
Socks4Builder {
phantom: PhantomData,
proxy: Some(ipv4),
to: None,
}
}
}
impl Socks4Builder<HasProxy> {
pub fn to(mut self, ipv4: SocketAddrV4) -> Socks4Builder<HasProxy> {
self.to = Some(ipv4);
self
}
pub fn build(self) -> crate::Result<Socks4> {
let proxy = self.proxy.ok_or(errors::Error::SocksProxyAddrNotSet)?;
let target = self.to.ok_or(errors::Error::SocksTargetAddrNotSet)?;
Ok(Socks4 { proxy, target })
}
}
pub struct Socks4 {
proxy: SocketAddrV4,
target: SocketAddrV4,
}
impl Socks4 {
pub fn builder() -> Socks4Builder {
Socks4Builder {
phantom: PhantomData,
proxy: None,
to: None,
}
}
pub fn set_proxy(mut self, ipv4: SocketAddrV4) -> Self {
self.proxy = ipv4;
self
}
pub fn set_target(mut self, ipv4: SocketAddrV4) -> Self {
self.target = ipv4;
self
}
pub async fn connect(&self) -> crate::Result<TcpStream> {
let proxy = self.proxy;
let target = self.target;
let mut conn = TcpStream::connect(proxy).await?;
let mut packet = [0u8; 9];
packet[0] = VERSION_4;
packet[1] = CONNECT_REQUEST;
packet[2..4].copy_from_slice(&target.port().to_be_bytes());
packet[4..8].copy_from_slice(&target.ip().octets());
packet[8] = NULL;
conn.write_all(&packet).await?;
let mut reply = [0u8; 8];
let n = conn.read(&mut reply[..]).await?;
if n == 0 {
return Err(errors::Error::ProxyResponseNotOk(
"proxy server didn't reply".into(),
));
}
Reply::try_from(reply[1])?;
Ok(conn)
}
#[cfg(feature = "tls")]
pub async fn tls(
&self,
stream: TcpStream,
config: Arc<ClientConfig>,
sni: ServerName<'static>,
) -> crate::Result<TlsStream<TcpStream>> {
let connector = TlsConnector::from(config);
let conn = connector.connect(sni, stream).await?;
Ok(conn)
}
}