use crate::error::{Error, Result};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
const SOCKS5_VERSION: u8 = 0x05;
const SOCKS5_NO_AUTHENTICATION: u8 = 0x00;
const SOCKS5_NO_ACCEPTABLE_METHODS: u8 = 0xFF;
const SOCKS5_CMD_CONNECT: u8 = 0x01;
const SOCKS5_ATYP_IPV4: u8 = 0x01;
const SOCKS5_ATYP_DOMAIN: u8 = 0x03;
const SOCKS5_ATYP_IPV6: u8 = 0x04;
const SOCKS5_REP_SUCCESS: u8 = 0x00;
const SOCKS5_REP_COMMAND_NOT_SUPPORTED: u8 = 0x07;
const SOCKS5_REP_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 0x08;
#[derive(Debug, Clone)]
pub enum TargetAddr {
Ipv4([u8; 4], u16),
Ipv6([u8; 16], u16),
Domain(String, u16),
}
impl TargetAddr {
pub fn to_host_port(&self) -> String {
match self {
TargetAddr::Ipv4(ip, port) => {
format!("{}.{}.{}.{}:{}", ip[0], ip[1], ip[2], ip[3], port)
}
TargetAddr::Ipv6(ip, port) => {
let segments: Vec<u16> = (0..8)
.map(|i| u16::from_be_bytes([ip[i * 2], ip[i * 2 + 1]]))
.collect();
format!(
"[{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}]:{}",
segments[0],
segments[1],
segments[2],
segments[3],
segments[4],
segments[5],
segments[6],
segments[7],
port
)
}
TargetAddr::Domain(domain, port) => format!("{}:{}", domain, port),
}
}
pub fn host(&self) -> String {
match self {
TargetAddr::Ipv4(ip, _) => format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]),
TargetAddr::Ipv6(ip, _) => {
let segments: Vec<u16> = (0..8)
.map(|i| u16::from_be_bytes([ip[i * 2], ip[i * 2 + 1]]))
.collect();
format!(
"{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}",
segments[0],
segments[1],
segments[2],
segments[3],
segments[4],
segments[5],
segments[6],
segments[7]
)
}
TargetAddr::Domain(domain, _) => domain.clone(),
}
}
pub fn port(&self) -> u16 {
match self {
TargetAddr::Ipv4(_, port) | TargetAddr::Ipv6(_, port) | TargetAddr::Domain(_, port) => *port,
}
}
}
pub struct Socks5Server;
impl Socks5Server {
pub async fn handle_handshake(stream: &mut TcpStream) -> Result<TargetAddr> {
let mut buf = [0u8; 2];
stream.read_exact(&mut buf).await?;
let version = buf[0];
let nmethods = buf[1];
if version != SOCKS5_VERSION {
return Err(Error::proxy_error(format!(
"Unsupported SOCKS version: {}",
version
)));
}
Self::handle_handshake_internal(stream, nmethods).await
}
pub async fn handle_handshake_with_version(stream: &mut TcpStream) -> Result<TargetAddr> {
let mut buf = [0u8; 1];
stream.read_exact(&mut buf).await?;
let nmethods = buf[0];
Self::handle_handshake_internal(stream, nmethods).await
}
async fn handle_handshake_internal(stream: &mut TcpStream, nmethods: u8) -> Result<TargetAddr> {
let mut methods = vec![0u8; nmethods as usize];
stream.read_exact(&mut methods).await?;
let selected_method = if methods.contains(&SOCKS5_NO_AUTHENTICATION) {
SOCKS5_NO_AUTHENTICATION
} else {
SOCKS5_NO_ACCEPTABLE_METHODS
};
let response = [SOCKS5_VERSION, selected_method];
stream.write_all(&response).await?;
if selected_method == SOCKS5_NO_ACCEPTABLE_METHODS {
return Err(Error::proxy_error(
"No acceptable authentication method".to_string(),
));
}
let mut buf = [0u8; 4];
stream.read_exact(&mut buf).await?;
let version = buf[0];
let cmd = buf[1];
let atyp = buf[3];
if version != SOCKS5_VERSION {
return Err(Error::proxy_error(format!(
"Invalid SOCKS version in request: {}",
version
)));
}
if cmd != SOCKS5_CMD_CONNECT {
Self::send_reply(stream, SOCKS5_REP_COMMAND_NOT_SUPPORTED).await?;
return Err(Error::proxy_error(format!("Unsupported command: {}", cmd)));
}
let target_addr = match atyp {
SOCKS5_ATYP_IPV4 => {
let mut addr = [0u8; 4];
stream.read_exact(&mut addr).await?;
let mut port_buf = [0u8; 2];
stream.read_exact(&mut port_buf).await?;
let port = u16::from_be_bytes(port_buf);
TargetAddr::Ipv4(addr, port)
}
SOCKS5_ATYP_IPV6 => {
let mut addr = [0u8; 16];
stream.read_exact(&mut addr).await?;
let mut port_buf = [0u8; 2];
stream.read_exact(&mut port_buf).await?;
let port = u16::from_be_bytes(port_buf);
TargetAddr::Ipv6(addr, port)
}
SOCKS5_ATYP_DOMAIN => {
let mut len_buf = [0u8; 1];
stream.read_exact(&mut len_buf).await?;
let len = len_buf[0] as usize;
let mut domain = vec![0u8; len];
stream.read_exact(&mut domain).await?;
let mut port_buf = [0u8; 2];
stream.read_exact(&mut port_buf).await?;
let port = u16::from_be_bytes(port_buf);
let domain_str = String::from_utf8(domain)
.map_err(|_| Error::proxy_error("Invalid domain name".to_string()))?;
TargetAddr::Domain(domain_str, port)
}
_ => {
Self::send_reply(stream, SOCKS5_REP_ADDRESS_TYPE_NOT_SUPPORTED).await?;
return Err(Error::proxy_error(format!(
"Unsupported address type: {}",
atyp
)));
}
};
Self::send_reply(stream, SOCKS5_REP_SUCCESS).await?;
Ok(target_addr)
}
async fn send_reply(stream: &mut TcpStream, reply_code: u8) -> Result<()> {
let response = [
SOCKS5_VERSION,
reply_code,
0x00, SOCKS5_ATYP_IPV4,
0x00,
0x00,
0x00,
0x00, 0x00,
0x00, ];
stream.write_all(&response).await?;
Ok(())
}
}