use bytes::{Buf, BufMut, BytesMut};
use std::fmt::Display;
use std::io;
use std::net::{Ipv4Addr, Ipv6Addr};
#[cfg(feature = "client")]
pub mod client;
#[cfg(feature = "server")]
pub mod server;
const SOCKS5_VERSION: u8 = 5;
const SOCKS5_RESERVED: u8 = 0x00;
const SOCKS5_USERNAME_AUTH_VER: u8 = 0x01;
enum Socks5Command {
Connect = 0x01,
Bind = 0x02,
UdpAssociate = 0x03,
}
impl TryFrom<u8> for Socks5Command {
type Error = io::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
v if v == Socks5Command::Connect as u8 => Ok(Socks5Command::Connect),
v if v == Socks5Command::Bind as u8 => Ok(Socks5Command::Bind),
v if v == Socks5Command::UdpAssociate as u8 => Ok(Socks5Command::UdpAssociate),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unsupported SOCKS5 command: {}", value),
)),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Socks5Status {
RequestGranted = 0x00,
GeneralFailure = 0x01,
ConnectionNotAllowed = 0x02,
NetworkUnreachable = 0x03,
HostUnreachable = 0x04,
ConnectionRefused = 0x05,
TtlExpired = 0x06,
CommandNotSupported = 0x07,
AddressTypeNotSupported = 0x08,
}
impl TryFrom<u8> for Socks5Status {
type Error = io::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
v if v == Socks5Status::RequestGranted as u8 => Ok(Socks5Status::RequestGranted),
v if v == Socks5Status::GeneralFailure as u8 => Ok(Socks5Status::GeneralFailure),
v if v == Socks5Status::ConnectionNotAllowed as u8 => {
Ok(Socks5Status::ConnectionNotAllowed)
}
v if v == Socks5Status::NetworkUnreachable as u8 => {
Ok(Socks5Status::NetworkUnreachable)
}
v if v == Socks5Status::HostUnreachable as u8 => Ok(Socks5Status::HostUnreachable),
v if v == Socks5Status::ConnectionRefused as u8 => Ok(Socks5Status::ConnectionRefused),
v if v == Socks5Status::TtlExpired as u8 => Ok(Socks5Status::TtlExpired),
v if v == Socks5Status::CommandNotSupported as u8 => {
Ok(Socks5Status::CommandNotSupported)
}
v if v == Socks5Status::AddressTypeNotSupported as u8 => {
Ok(Socks5Status::AddressTypeNotSupported)
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unsupported SOCKS5 command: {}", value),
)),
}
}
}
enum Socks5AddressType {
IPv4 = 0x01,
Domain = 0x03,
IPv6 = 0x04,
}
impl TryFrom<u8> for Socks5AddressType {
type Error = io::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
v if v == Socks5AddressType::IPv4 as u8 => Ok(Socks5AddressType::IPv4),
v if v == Socks5AddressType::Domain as u8 => Ok(Socks5AddressType::Domain),
v if v == Socks5AddressType::IPv6 as u8 => Ok(Socks5AddressType::IPv6),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unsupported SOCKS5 address type: {}", value),
)),
}
}
}
#[derive(Clone)]
pub enum Socks5Address {
IPv4(Ipv4Addr),
Domain(String),
IPv6(Ipv6Addr),
}
impl From<&str> for Socks5Address {
fn from(value: &str) -> Self {
if let Ok(ipv4) = value.parse::<Ipv4Addr>() {
Self::IPv4(ipv4)
} else if let Ok(ipv6) = value.parse::<Ipv6Addr>() {
Self::IPv6(ipv6)
} else if value.contains('.')
&& value
.chars()
.all(|c| c.is_alphanumeric() || c == '.' || c == '-')
{
Self::Domain(value.to_string())
} else {
eprintln!("Invalid address format: '{}', treating as domain", value);
Self::Domain(value.to_string())
}
}
}
impl Socks5Address {
fn to_bytes(&self, buf: &mut BytesMut) {
match self {
Self::IPv4(ip) => {
buf.put_u8(Socks5AddressType::IPv4 as u8);
buf.extend_from_slice(&ip.octets());
}
Self::Domain(domain) => {
buf.put_u8(Socks5AddressType::Domain as u8);
let len = domain.len() as u8;
buf.put_u8(len);
buf.extend_from_slice(domain.as_bytes());
}
Self::IPv6(ip) => {
buf.put_u8(Socks5AddressType::IPv6 as u8);
buf.extend_from_slice(&ip.octets());
}
}
}
fn parse(atyp: Socks5AddressType, src: &mut BytesMut) -> Result<Self, io::Error> {
match atyp {
Socks5AddressType::IPv4 => {
if src.len() < 4 {
return Err(io::Error::new(
io::ErrorKind::WouldBlock,
"Not enough data for IPv4",
));
}
let ip = Ipv4Addr::new(src[0], src[1], src[2], src[3]);
src.advance(4);
Ok(Socks5Address::IPv4(ip))
}
Socks5AddressType::Domain => {
if src.is_empty() {
return Err(io::Error::new(
io::ErrorKind::WouldBlock,
"Not enough data for domain length",
));
}
let len = src[0] as usize;
src.advance(1);
if src.len() < len {
return Err(io::Error::new(
io::ErrorKind::WouldBlock,
"Not enough data for domain",
));
}
let domain = String::from_utf8_lossy(&src[..len]).to_string();
src.advance(len);
Ok(Socks5Address::Domain(domain))
}
Socks5AddressType::IPv6 => {
if src.len() < 16 {
return Err(io::Error::new(
io::ErrorKind::WouldBlock,
"Not enough data for IPv6",
));
}
let ip = Ipv6Addr::new(
u16::from_be_bytes([src[0], src[1]]),
u16::from_be_bytes([src[2], src[3]]),
u16::from_be_bytes([src[4], src[5]]),
u16::from_be_bytes([src[6], src[7]]),
u16::from_be_bytes([src[8], src[9]]),
u16::from_be_bytes([src[10], src[11]]),
u16::from_be_bytes([src[12], src[13]]),
u16::from_be_bytes([src[14], src[15]]),
);
src.advance(16);
Ok(Socks5Address::IPv6(ip))
}
}
}
}
impl Display for Socks5Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Socks5Address::IPv4(ip) => write!(f, "{}", ip),
Socks5Address::Domain(domain) => write!(f, "{}", domain),
Socks5Address::IPv6(ip) => write!(f, "{}", ip),
}
}
}
struct Socks5Response {
response: Socks5Status,
address: Socks5Address,
port: u16,
}
pub struct Socks5Request {
command: Socks5Command,
address: Socks5Address,
port: u16,
}
#[derive(Clone, PartialEq)]
enum AuthMethod {
NoAuth = 0x00,
Gssapi = 0x01,
UsernamePassword = 0x02,
Chap = 0x03,
Cram = 0x05,
Ssl = 0x06,
Nds = 0x07,
Maf = 0x08,
Json = 0x09,
}
impl Display for AuthMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
AuthMethod::NoAuth => "NoAuth",
AuthMethod::Gssapi => "Gssapi",
AuthMethod::UsernamePassword => "UsernamePassword",
AuthMethod::Chap => "Chap",
AuthMethod::Cram => "Cram",
AuthMethod::Ssl => "Ssl",
AuthMethod::Nds => "Nds",
AuthMethod::Maf => "Maf",
AuthMethod::Json => "Json",
};
write!(f, "{}", name)
}
}
impl TryFrom<u8> for AuthMethod {
type Error = io::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
v if v == Self::NoAuth as u8 => Ok(Self::NoAuth),
v if v == Self::Gssapi as u8 => Ok(Self::Gssapi),
v if v == Self::UsernamePassword as u8 => Ok(Self::UsernamePassword),
v if v == Self::Chap as u8 => Ok(Self::Chap),
v if v == Self::Cram as u8 => Ok(Self::Cram),
v if v == Self::Ssl as u8 => Ok(Self::Ssl),
v if v == Self::Nds as u8 => Ok(Self::Nds),
v if v == Self::Maf as u8 => Ok(Self::Maf),
v if v == Self::Json as u8 => Ok(Self::Json),
v => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unknown auth method {v}"),
)),
}
}
}
fn decode_res<S>(src: &mut BytesMut) -> io::Result<Option<(S, Socks5Address, u16)>>
where
S: TryFrom<u8>,
std::io::Error: From<<S as TryFrom<u8>>::Error>,
{
if src.len() < 4 {
return Ok(None);
}
let version = src[0];
if version != SOCKS5_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid SOCKS version",
));
}
let res = S::try_from(src[1])?;
let atyp = Socks5AddressType::try_from(src[3])?;
src.advance(4);
let address = match Socks5Address::parse(atyp, src) {
Ok(addr) => addr,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(None),
Err(e) => return Err(e),
};
if src.len() < 2 {
return Ok(None);
}
let port = u16::from_be_bytes([src[0], src[1]]);
src.advance(2);
Ok(Some((res, address, port)))
}