use std::{
net::{IpAddr, SocketAddr},
time::Duration,
};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
};
use super::error::Socks5Error;
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(2);
const VERSION: u8 = 5;
const NOAUTH: u8 = 0;
const METHODS: u8 = 1;
const CMD_CONNECT: u8 = 1;
const RESPONSE_SUCCESS: u8 = 0;
const RSV: u8 = 0;
const ADDR_TYPE_IPV4: u8 = 1;
const ADDR_TYPE_IPV6: u8 = 4;
pub(crate) async fn create_socks5(
proxy: SocketAddr,
ip_addr: IpAddr,
port: u16,
) -> Result<TcpStream, Socks5Error> {
let timeout = tokio::time::timeout(CONNECTION_TIMEOUT, TcpStream::connect(proxy))
.await
.map_err(|_| Socks5Error::ConnectionTimeout)?;
let dest_ip_bytes = match ip_addr {
IpAddr::V4(ipv4) => ipv4.octets().to_vec(),
IpAddr::V6(ipv6) => ipv6.octets().to_vec(),
};
let dest_port_bytes = port.to_be_bytes();
let ip_type_byte = match ip_addr {
IpAddr::V4(_) => ADDR_TYPE_IPV4,
IpAddr::V6(_) => ADDR_TYPE_IPV6,
};
let mut tcp_stream = timeout.map_err(|_| Socks5Error::ConnectionFailed)?;
tcp_stream.write_all(&[VERSION, METHODS, NOAUTH]).await?;
let mut buf = [0_u8; 2];
tcp_stream.read_exact(&mut buf).await?;
if buf[0] != VERSION {
return Err(Socks5Error::WrongVersion);
}
if buf[1] != NOAUTH {
return Err(Socks5Error::AuthRequired);
}
tcp_stream
.write_all(&[VERSION, CMD_CONNECT, RSV, ip_type_byte])
.await?;
tcp_stream.write_all(&dest_ip_bytes).await?;
tcp_stream.write_all(&dest_port_bytes).await?;
let mut buf = [0_u8; 4];
tcp_stream.read_exact(&mut buf).await?;
if buf[0] != VERSION {
return Err(Socks5Error::WrongVersion);
}
if buf[1] != RESPONSE_SUCCESS {
return Err(Socks5Error::ConnectionFailed);
}
match buf[3] {
ADDR_TYPE_IPV4 => {
let mut buf = [0_u8; 6];
tcp_stream.read_exact(&mut buf).await?;
}
ADDR_TYPE_IPV6 => {
let mut buf = [0_u8; 18];
tcp_stream.read_exact(&mut buf).await?;
}
_ => return Err(Socks5Error::ConnectionFailed),
}
Ok(tcp_stream)
}