use bytes::{BufMut, BytesMut};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::proxy::ProxyAuth;
use crate::{ErrorKind, ProxyError, ProxyResult};
const PROXY_VERSION: u8 = 0x04;
const CONNECT_COMMAND: u8 = 0x01;
#[derive(Debug, PartialEq, Eq)]
enum ResponseStatus {
Success,
RequestRejected,
IdentdError,
AuthError,
UnknownResponse,
}
impl ResponseStatus {
pub fn from(id: u8) -> Self {
match id {
0x5a => Self::Success,
0x5b => Self::RequestRejected,
0x5c => Self::IdentdError,
0x5d => Self::AuthError,
_ => Self::UnknownResponse,
}
}
}
pub async fn connect_socks4(stream: &mut TcpStream, target_host: String, target_port: u16, auth: &Option<ProxyAuth>) -> ProxyResult<()> {
let mut req = BytesMut::with_capacity(512);
req.put_u8(PROXY_VERSION);
req.put_u8(CONNECT_COMMAND);
req.put_u16(target_port);
let ipv4_addr = target_host.parse::<std::net::Ipv4Addr>();
if let Ok(ipv4) = ipv4_addr {
req.put_slice(&ipv4.octets());
} else {
req.put_slice(&[0x00, 0x00, 0x00, 0x01]);
}
if let Some(a) = auth {
let user_id = match a {
ProxyAuth::Ident { user_id } => user_id,
_ => {
return Err(ProxyError::new(
ErrorKind::AuthFailed,
"specified incorrect authorization method for SOCKS4",
));
}
};
req.put_slice(user_id.as_bytes());
} else {
req.put_u8(0x00);
}
if ipv4_addr.is_err() {
if target_host.len() > 255 {
return Err(ProxyError::new(ErrorKind::InvalidData, "target host is too long"));
}
req.put_slice(target_host.as_bytes());
req.put_u8(0x00);
}
stream.write_all(&req).await?;
let mut resp = [0u8; 8];
stream.read_exact(&mut resp).await?;
if resp[0] != 0x00 {
return Err(ProxyError::new(ErrorKind::InvalidData, "invalid proxy response"));
}
let response_status = ResponseStatus::from(resp[1]);
if response_status != ResponseStatus::Success {
return Err(ProxyError::new(
ErrorKind::NotConnected,
format!("unsuccessful proxy response status: {:?}", response_status),
));
}
Ok(())
}