1#[forbid(unsafe_code)]
2#[macro_use]
3extern crate log;
4
5pub mod client;
6pub mod server;
7pub mod util;
8
9#[cfg(feature = "socks4")]
10pub mod socks4;
11
12use anyhow::Context;
13use std::fmt;
14use std::io;
15use thiserror::Error;
16use util::target_addr::read_address;
17use util::target_addr::TargetAddr;
18use util::target_addr::ToTargetAddr;
19
20use tokio::io::AsyncReadExt;
21
22#[rustfmt::skip]
23pub mod consts {
24 pub const SOCKS5_VERSION: u8 = 0x05;
25
26 pub const SOCKS5_AUTH_METHOD_NONE: u8 = 0x00;
27 pub const SOCKS5_AUTH_METHOD_GSSAPI: u8 = 0x01;
28 pub const SOCKS5_AUTH_METHOD_PASSWORD: u8 = 0x02;
29 pub const SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE: u8 = 0xff;
30
31 pub const SOCKS5_CMD_TCP_CONNECT: u8 = 0x01;
32 pub const SOCKS5_CMD_TCP_BIND: u8 = 0x02;
33 pub const SOCKS5_CMD_UDP_ASSOCIATE: u8 = 0x03;
34
35 pub const SOCKS5_ADDR_TYPE_IPV4: u8 = 0x01;
36 pub const SOCKS5_ADDR_TYPE_DOMAIN_NAME: u8 = 0x03;
37 pub const SOCKS5_ADDR_TYPE_IPV6: u8 = 0x04;
38
39 pub const SOCKS5_REPLY_SUCCEEDED: u8 = 0x00;
40 pub const SOCKS5_REPLY_GENERAL_FAILURE: u8 = 0x01;
41 pub const SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: u8 = 0x02;
42 pub const SOCKS5_REPLY_NETWORK_UNREACHABLE: u8 = 0x03;
43 pub const SOCKS5_REPLY_HOST_UNREACHABLE: u8 = 0x04;
44 pub const SOCKS5_REPLY_CONNECTION_REFUSED: u8 = 0x05;
45 pub const SOCKS5_REPLY_TTL_EXPIRED: u8 = 0x06;
46 pub const SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: u8 = 0x07;
47 pub const SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 0x08;
48}
49
50#[derive(Debug, PartialEq)]
51pub enum Socks5Command {
52 TCPConnect,
53 TCPBind,
54 UDPAssociate,
55}
56
57#[allow(dead_code)]
58impl Socks5Command {
59 #[inline]
60 #[rustfmt::skip]
61 fn as_u8(&self) -> u8 {
62 match self {
63 Socks5Command::TCPConnect => consts::SOCKS5_CMD_TCP_CONNECT,
64 Socks5Command::TCPBind => consts::SOCKS5_CMD_TCP_BIND,
65 Socks5Command::UDPAssociate => consts::SOCKS5_CMD_UDP_ASSOCIATE,
66 }
67 }
68
69 #[inline]
70 #[rustfmt::skip]
71 fn from_u8(code: u8) -> Option<Socks5Command> {
72 match code {
73 consts::SOCKS5_CMD_TCP_CONNECT => Some(Socks5Command::TCPConnect),
74 consts::SOCKS5_CMD_TCP_BIND => Some(Socks5Command::TCPBind),
75 consts::SOCKS5_CMD_UDP_ASSOCIATE => Some(Socks5Command::UDPAssociate),
76 _ => None,
77 }
78 }
79}
80
81#[derive(Debug, PartialEq)]
82pub enum AuthenticationMethod {
83 None,
84 Password { username: String, password: String },
85}
86
87impl AuthenticationMethod {
88 #[inline]
89 #[rustfmt::skip]
90 fn as_u8(&self) -> u8 {
91 match self {
92 AuthenticationMethod::None => consts::SOCKS5_AUTH_METHOD_NONE,
93 AuthenticationMethod::Password {..} =>
94 consts::SOCKS5_AUTH_METHOD_PASSWORD
95 }
96 }
97
98 #[inline]
99 #[rustfmt::skip]
100 fn from_u8(code: u8) -> Option<AuthenticationMethod> {
101 match code {
102 consts::SOCKS5_AUTH_METHOD_NONE => Some(AuthenticationMethod::None),
103 consts::SOCKS5_AUTH_METHOD_PASSWORD => Some(AuthenticationMethod::Password { username: "test".to_string(), password: "test".to_string()}),
104 _ => None,
105 }
106 }
107}
108
109impl fmt::Display for AuthenticationMethod {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match *self {
112 AuthenticationMethod::None => f.write_str("AuthenticationMethod::None"),
113 AuthenticationMethod::Password { .. } => f.write_str("AuthenticationMethod::Password"),
114 }
115 }
116}
117
118#[derive(Error, Debug)]
131pub enum SocksError {
132 #[error("i/o error: {0}")]
133 Io(#[from] io::Error),
134 #[error("the data for key `{0}` is not available")]
135 Redaction(String),
136 #[error("invalid header (expected {expected:?}, found {found:?})")]
137 InvalidHeader { expected: String, found: String },
138
139 #[error("Auth method unacceptable `{0:?}`.")]
140 AuthMethodUnacceptable(Vec<u8>),
141 #[error("Unsupported SOCKS version `{0}`.")]
142 UnsupportedSocksVersion(u8),
143 #[error("Domain exceeded max sequence length")]
144 ExceededMaxDomainLen(usize),
145 #[error("Authentication failed `{0}`")]
146 AuthenticationFailed(String),
147 #[error("Authentication rejected `{0}`")]
148 AuthenticationRejected(String),
149
150 #[error("Error with reply: {0}.")]
151 ReplyError(#[from] ReplyError),
152
153 #[cfg(feature = "socks4")]
154 #[error("Error with reply: {0}.")]
155 ReplySocks4Error(#[from] socks4::ReplyError),
156
157 #[error("Argument input error: `{0}`.")]
158 ArgumentInputError(&'static str),
159
160 #[error(transparent)]
162 Other(#[from] anyhow::Error),
163}
164
165pub type Result<T, E = SocksError> = core::result::Result<T, E>;
166
167#[derive(Error, Debug, Copy, Clone)]
169pub enum ReplyError {
170 #[error("Succeeded")]
171 Succeeded,
172 #[error("General failure")]
173 GeneralFailure,
174 #[error("Connection not allowed by ruleset")]
175 ConnectionNotAllowed,
176 #[error("Network unreachable")]
177 NetworkUnreachable,
178 #[error("Host unreachable")]
179 HostUnreachable,
180 #[error("Connection refused")]
181 ConnectionRefused,
182 #[error("TTL expired")]
183 TtlExpired,
184 #[error("Command not supported")]
185 CommandNotSupported,
186 #[error("Address type not supported")]
187 AddressTypeNotSupported,
188 }
190
191impl ReplyError {
192 #[inline]
193 #[rustfmt::skip]
194 pub fn as_u8(self) -> u8 {
195 match self {
196 ReplyError::Succeeded => consts::SOCKS5_REPLY_SUCCEEDED,
197 ReplyError::GeneralFailure => consts::SOCKS5_REPLY_GENERAL_FAILURE,
198 ReplyError::ConnectionNotAllowed => consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED,
199 ReplyError::NetworkUnreachable => consts::SOCKS5_REPLY_NETWORK_UNREACHABLE,
200 ReplyError::HostUnreachable => consts::SOCKS5_REPLY_HOST_UNREACHABLE,
201 ReplyError::ConnectionRefused => consts::SOCKS5_REPLY_CONNECTION_REFUSED,
202 ReplyError::TtlExpired => consts::SOCKS5_REPLY_TTL_EXPIRED,
203 ReplyError::CommandNotSupported => consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED,
204 ReplyError::AddressTypeNotSupported => consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED,
205}
207 }
208
209 #[inline]
210 #[rustfmt::skip]
211 pub fn from_u8(code: u8) -> ReplyError {
212 match code {
213 consts::SOCKS5_REPLY_SUCCEEDED => ReplyError::Succeeded,
214 consts::SOCKS5_REPLY_GENERAL_FAILURE => ReplyError::GeneralFailure,
215 consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED => ReplyError::ConnectionNotAllowed,
216 consts::SOCKS5_REPLY_NETWORK_UNREACHABLE => ReplyError::NetworkUnreachable,
217 consts::SOCKS5_REPLY_HOST_UNREACHABLE => ReplyError::HostUnreachable,
218 consts::SOCKS5_REPLY_CONNECTION_REFUSED => ReplyError::ConnectionRefused,
219 consts::SOCKS5_REPLY_TTL_EXPIRED => ReplyError::TtlExpired,
220 consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED => ReplyError::CommandNotSupported,
221 consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED => ReplyError::AddressTypeNotSupported,
222_ => unreachable!("ReplyError code unsupported."),
224 }
225 }
226}
227
228pub fn new_udp_header<T: ToTargetAddr>(target_addr: T) -> Result<Vec<u8>> {
251 let mut header = vec![
252 0, 0, 0, ];
255 header.append(&mut target_addr.to_target_addr()?.to_be_bytes()?);
256
257 Ok(header)
258}
259
260pub async fn parse_udp_request<'a>(mut req: &'a [u8]) -> Result<(u8, TargetAddr, &'a [u8])> {
262 let rsv = read_exact!(req, [0u8; 2]).context("Malformed request")?;
263
264 if !rsv.eq(&[0u8; 2]) {
265 return Err(ReplyError::GeneralFailure.into());
266 }
267
268 let [frag, atyp] = read_exact!(req, [0u8; 2]).context("Malformed request")?;
269
270 let target_addr = read_address(&mut req, atyp).await.map_err(|e| {
271 error!("{:#}", e);
273 ReplyError::AddressTypeNotSupported
275 })?;
276
277 Ok((frag, target_addr, req))
278}
279
280#[cfg(test)]
281mod test {
282 use anyhow::Result;
283 use tokio::{
284 net::{TcpListener, TcpStream, UdpSocket},
285 sync::oneshot::Sender,
286 };
287
288 use crate::{
289 client,
290 server::{self, SimpleUserPassword},
291 };
292 use std::{
293 net::{SocketAddr, ToSocketAddrs},
294 num::ParseIntError,
295 sync::Arc,
296 };
297 use tokio::io::{AsyncReadExt, AsyncWriteExt};
298 use tokio::sync::oneshot;
299 use tokio_test::block_on;
300
301 fn init() {
302 let _ = env_logger::builder().is_test(true).try_init();
303 }
304
305 async fn setup_socks_server(
306 proxy_addr: &str,
307 auth: Option<SimpleUserPassword>,
308 tx: Sender<SocketAddr>,
309 ) -> Result<()> {
310 let mut config = server::Config::default();
311 config.set_udp_support(true);
312 match auth {
313 None => {}
314 Some(up) => {
315 config.set_authentication(up);
316 }
317 }
318
319 let config = Arc::new(config);
320 let listener = TcpListener::bind(proxy_addr).await?;
321 tx.send(listener.local_addr()?).unwrap();
322 loop {
323 let (stream, _) = listener.accept().await?;
324 let mut socks5_socket = server::Socks5Socket::new(stream, config.clone());
325 socks5_socket.set_reply_ip(proxy_addr.parse::<SocketAddr>().unwrap().ip());
326
327 socks5_socket.upgrade_to_socks5().await?;
328 }
329 }
330
331 async fn google(mut socket: TcpStream) -> Result<()> {
332 socket.write_all(b"GET / HTTP/1.0\r\n\r\n").await?;
333 let mut result = vec![];
334 socket.read_to_end(&mut result).await?;
335
336 println!("{}", String::from_utf8_lossy(&result));
337 assert!(result.starts_with(b"HTTP/1.0"));
338 assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>"));
339
340 Ok(())
341 }
342
343 #[test]
344 fn google_no_auth() {
345 init();
346 block_on(async {
347 let (tx, rx) = oneshot::channel();
348 tokio::spawn(setup_socks_server("[::1]:0", None, tx));
349
350 let socket = client::Socks5Stream::connect(
351 rx.await.unwrap(),
352 "google.com".to_owned(),
353 80,
354 client::Config::default(),
355 )
356 .await
357 .unwrap();
358 google(socket.get_socket()).await.unwrap();
359 });
360 }
361
362 #[test]
363 fn mock_udp_assosiate_no_auth() {
364 init();
365 block_on(async {
366 const MOCK_ADDRESS: &str = "[::1]:40235";
367
368 let (tx, rx) = oneshot::channel();
369 tokio::spawn(setup_socks_server("[::1]:0", None, tx));
370 let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap();
371
372 let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0")
375 .await
376 .unwrap();
377 let mock_udp_server = UdpSocket::bind(MOCK_ADDRESS).await.unwrap();
378
379 tunnel
380 .send_to(
381 b"hello world!",
382 MOCK_ADDRESS.to_socket_addrs().unwrap().next().unwrap(),
383 )
384 .await
385 .unwrap();
386 println!("Send packet to {}", MOCK_ADDRESS);
387
388 let mut buf = [0; 13];
389 let (len, addr) = mock_udp_server.recv_from(&mut buf).await.unwrap();
390 assert_eq!(len, 12);
391 assert_eq!(&buf[..12], b"hello world!");
392
393 mock_udp_server
394 .send_to(b"hello world!", addr)
395 .await
396 .unwrap();
397
398 println!("Recieve packet from {}", MOCK_ADDRESS);
399 let len = tunnel.recv_from(&mut buf).await.unwrap().0;
400 assert_eq!(len, 12);
401 assert_eq!(&buf[..12], b"hello world!");
402 });
403 }
404
405 #[test]
406 fn dns_udp_assosiate_no_auth() {
407 init();
408 block_on(async {
409 const DNS_SERVER: &str = "1.1.1.1:53";
410
411 let (tx, rx) = oneshot::channel();
412 tokio::spawn(setup_socks_server("[::1]:0", None, tx));
413 let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap();
414
415 let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0")
418 .await
419 .unwrap();
420
421 #[rustfmt::skip]
422 tunnel.send_to(
423 &decode_hex(&(
424 "AAAA".to_owned() + "0100" + "0001" + "0000" + "0000" + "0000" + "076578616d706c65"+ "03636f6d00" + "0001" + "0001" ))
435 .unwrap(),
436 DNS_SERVER.to_socket_addrs().unwrap().next().unwrap(),
437 ).await.unwrap();
438 println!("Send packet to {}", DNS_SERVER);
439
440 let mut buf = [0; 128];
441 println!("Recieve packet from {}", DNS_SERVER);
442 tunnel.recv_from(&mut buf).await.unwrap();
443 println!("dns response {:?}", buf);
444
445 #[rustfmt::skip]
446 assert!(buf.starts_with(&decode_hex(&(
447 "AAAA".to_owned() + "8180" + "0001" )).unwrap()));
451 });
452 }
453
454 fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
455 (0..s.len())
456 .step_by(2)
457 .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
458 .collect()
459 }
460}