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#[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 eprintln!("Invalid address format: '{}', treating as domain", value);
125 Self::Domain(value.to_string())
126 }
127 }
128}
129impl Socks5Address {
130 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 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 NoAuth = 0x00,
232 Gssapi = 0x01,
234 UsernamePassword = 0x02,
236 Chap = 0x03,
238 Cram = 0x05,
240 Ssl = 0x06,
242 Nds = 0x07,
244 Maf = 0x08,
246 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); 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}