socks5x/
lib.rs

1use bytes::{Buf, BufMut, BytesMut};
2use std::fmt::Display;
3use std::io;
4use std::net::{Ipv4Addr, Ipv6Addr};
5
6#[cfg(feature = "client")]
7pub mod client;
8#[cfg(feature = "server")]
9pub mod server;
10
11const SOCKS5_VERSION: u8 = 5;
12const SOCKS5_RESERVED: u8 = 0x00;
13
14enum Socks5Command {
15    Connect = 0x01,
16    Bind = 0x02,
17    UdpAssociate = 0x03,
18}
19
20impl TryFrom<u8> for Socks5Command {
21    type Error = io::Error;
22
23    fn try_from(value: u8) -> Result<Self, Self::Error> {
24        match value {
25            v if v == Socks5Command::Connect as u8 => Ok(Socks5Command::Connect),
26            v if v == Socks5Command::Bind as u8 => Ok(Socks5Command::Bind),
27            v if v == Socks5Command::UdpAssociate as u8 => Ok(Socks5Command::UdpAssociate),
28            _ => Err(io::Error::new(
29                io::ErrorKind::InvalidData,
30                format!("Unsupported SOCKS5 command: {}", value),
31            )),
32        }
33    }
34}
35
36#[derive(Debug, PartialEq)]
37enum Scoks5Status {
38    RequestGranted = 0x00,
39    GeneralFailure = 0x01,
40    ConnectionNotAllowed = 0x02,
41    NetworkUnreachable = 0x03,
42    HostUnreachable = 0x04,
43    ConnectionRefused = 0x05,
44    TtlExpired = 0x06,
45    CommandNotSupported = 0x07,
46    AddressTypeNotSupported = 0x08,
47}
48
49impl TryFrom<u8> for Scoks5Status {
50    type Error = io::Error;
51
52    fn try_from(value: u8) -> Result<Self, Self::Error> {
53        match value {
54            v if v == Scoks5Status::RequestGranted as u8 => Ok(Scoks5Status::RequestGranted),
55            v if v == Scoks5Status::GeneralFailure as u8 => Ok(Scoks5Status::GeneralFailure),
56            v if v == Scoks5Status::ConnectionNotAllowed as u8 => {
57                Ok(Scoks5Status::ConnectionNotAllowed)
58            }
59            v if v == Scoks5Status::NetworkUnreachable as u8 => {
60                Ok(Scoks5Status::NetworkUnreachable)
61            }
62            v if v == Scoks5Status::HostUnreachable as u8 => Ok(Scoks5Status::HostUnreachable),
63            v if v == Scoks5Status::ConnectionRefused as u8 => Ok(Scoks5Status::ConnectionRefused),
64            v if v == Scoks5Status::TtlExpired as u8 => Ok(Scoks5Status::TtlExpired),
65            v if v == Scoks5Status::CommandNotSupported as u8 => {
66                Ok(Scoks5Status::CommandNotSupported)
67            }
68            v if v == Scoks5Status::AddressTypeNotSupported as u8 => {
69                Ok(Scoks5Status::AddressTypeNotSupported)
70            }
71            _ => Err(io::Error::new(
72                io::ErrorKind::InvalidData,
73                format!("Unsupported SOCKS5 command: {}", value),
74            )),
75        }
76    }
77}
78
79#[derive(Debug)]
80enum Socks5AddressType {
81    IPv4 = 0x01,
82    Domain = 0x03,
83    IPv6 = 0x04,
84}
85
86impl TryFrom<u8> for Socks5AddressType {
87    type Error = io::Error;
88
89    fn try_from(value: u8) -> Result<Self, Self::Error> {
90        match value {
91            v if v == Socks5AddressType::IPv4 as u8 => Ok(Socks5AddressType::IPv4),
92            v if v == Socks5AddressType::Domain as u8 => Ok(Socks5AddressType::Domain),
93            v if v == Socks5AddressType::IPv6 as u8 => Ok(Socks5AddressType::IPv6),
94            _ => Err(io::Error::new(
95                io::ErrorKind::InvalidData,
96                format!("Unsupported SOCKS5 address type: {}", value),
97            )),
98        }
99    }
100}
101
102/// Address types supported by SOCKS5 protocol.
103#[derive(Debug, Clone)]
104pub enum Socks5Address {
105    IPv4(Ipv4Addr),
106    Domain(String),
107    IPv6(Ipv6Addr),
108}
109
110impl From<&str> for Socks5Address {
111    fn from(value: &str) -> Self {
112        if let Ok(ipv4) = value.parse::<Ipv4Addr>() {
113            Self::IPv4(ipv4)
114        } else if let Ok(ipv6) = value.parse::<Ipv6Addr>() {
115            Self::IPv6(ipv6)
116        } else if value.contains('.')
117            && value
118                .chars()
119                .all(|c| c.is_alphanumeric() || c == '.' || c == '-')
120        {
121            Self::Domain(value.to_string())
122        } else {
123            // Fallback to a safe default if invalid
124            eprintln!("Invalid address format: '{}', treating as domain", value);
125            Self::Domain(value.to_string())
126        }
127    }
128}
129impl Socks5Address {
130    /// Encodes address to SOCKS5 wire format.
131    fn to_bytes(&self, buf: &mut BytesMut) {
132        match self {
133            Self::IPv4(ip) => {
134                buf.put_u8(Socks5AddressType::IPv4 as u8);
135                buf.extend_from_slice(&ip.octets());
136            }
137            Self::Domain(domain) => {
138                buf.put_u8(Socks5AddressType::Domain as u8);
139                let len = domain.len() as u8;
140                buf.put_u8(len);
141                buf.extend_from_slice(domain.as_bytes());
142            }
143            Self::IPv6(ip) => {
144                buf.put_u8(Socks5AddressType::IPv6 as u8);
145                buf.extend_from_slice(&ip.octets());
146            }
147        }
148    }
149    /// Parses address from SOCKS5 wire format.
150    fn parse(atyp: Socks5AddressType, src: &mut BytesMut) -> Result<Self, io::Error> {
151        match atyp {
152            Socks5AddressType::IPv4 => {
153                if src.len() < 4 {
154                    return Err(io::Error::new(
155                        io::ErrorKind::WouldBlock,
156                        "Not enough data for IPv4",
157                    ));
158                }
159                let ip = Ipv4Addr::new(src[0], src[1], src[2], src[3]);
160                src.advance(4);
161                Ok(Socks5Address::IPv4(ip))
162            }
163            Socks5AddressType::Domain => {
164                if src.is_empty() {
165                    return Err(io::Error::new(
166                        io::ErrorKind::WouldBlock,
167                        "Not enough data for domain length",
168                    ));
169                }
170                let len = src[0] as usize;
171                src.advance(1);
172                if src.len() < len {
173                    return Err(io::Error::new(
174                        io::ErrorKind::WouldBlock,
175                        "Not enough data for domain",
176                    ));
177                }
178                let domain = String::from_utf8_lossy(&src[..len]).to_string();
179                src.advance(len);
180                Ok(Socks5Address::Domain(domain))
181            }
182            Socks5AddressType::IPv6 => {
183                if src.len() < 16 {
184                    return Err(io::Error::new(
185                        io::ErrorKind::WouldBlock,
186                        "Not enough data for IPv6",
187                    ));
188                }
189                let ip = Ipv6Addr::new(
190                    u16::from_be_bytes([src[0], src[1]]),
191                    u16::from_be_bytes([src[2], src[3]]),
192                    u16::from_be_bytes([src[4], src[5]]),
193                    u16::from_be_bytes([src[6], src[7]]),
194                    u16::from_be_bytes([src[8], src[9]]),
195                    u16::from_be_bytes([src[10], src[11]]),
196                    u16::from_be_bytes([src[12], src[13]]),
197                    u16::from_be_bytes([src[14], src[15]]),
198                );
199                src.advance(16);
200                Ok(Socks5Address::IPv6(ip))
201            }
202        }
203    }
204}
205
206impl Display for Socks5Address {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        match self {
209            Socks5Address::IPv4(ip) => write!(f, "{}", ip),
210            Socks5Address::Domain(domain) => write!(f, "{}", domain),
211            Socks5Address::IPv6(ip) => write!(f, "{}", ip),
212        }
213    }
214}
215
216struct Socks5Response {
217    response: Scoks5Status,
218    address: Socks5Address,
219    port: u16,
220}
221
222pub struct Socks5Request {
223    command: Socks5Command,
224    address: Socks5Address,
225    port: u16,
226}
227
228#[derive(PartialEq)]
229enum AuthMethod {
230    /// No authentication (0x00)
231    NoAuth = 0x00,
232    /// GSSAPI (RFC 1961) (0x01)
233    Gssapi = 0x01,
234    /// Username/password (RFC 1929) (0x02)
235    UsernamePassword = 0x02,
236    /// Challenge-Handshake Authentication Protocol (0x03)
237    Chap = 0x03,
238    /// Challenge-Response Authentication Method (0x05)
239    Cram = 0x05,
240    /// Secure Sockets Layer (0x06)
241    Ssl = 0x06,
242    /// NDS Authentication (0x07)
243    Nds = 0x07,
244    /// Multi-Authentication Framework (0x08)
245    Maf = 0x08,
246    /// JSON Parameter Block (0x09)
247    Json = 0x09,
248}
249
250fn decode_res<S>(src: &mut BytesMut) -> io::Result<Option<(S, Socks5Address, u16)>>
251where
252    S: TryFrom<u8>,
253    std::io::Error: From<<S as TryFrom<u8>>::Error>,
254{
255    if src.len() < 4 {
256        return Ok(None);
257    }
258
259    let version = src[0];
260    if version != SOCKS5_VERSION {
261        return Err(io::Error::new(
262            io::ErrorKind::InvalidData,
263            "Invalid SOCKS version",
264        ));
265    }
266
267    let res = S::try_from(src[1])?;
268    let atyp = Socks5AddressType::try_from(src[3])?;
269    src.advance(4); // Consume VER, CMD, RSV, ATYP
270
271    let address = match Socks5Address::parse(atyp, src) {
272        Ok(addr) => addr,
273        Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(None),
274        Err(e) => return Err(e),
275    };
276
277    if src.len() < 2 {
278        return Ok(None);
279    }
280    let port = u16::from_be_bytes([src[0], src[1]]);
281    src.advance(2);
282
283    Ok(Some((res, address, port)))
284}