1#![forbid(unsafe_code)]
39#[macro_use]
40extern crate log;
41
42pub mod client;
43pub mod server;
44pub mod util;
45
46#[cfg(feature = "socks4")]
47pub mod socks4;
48
49use std::fmt;
50use std::io;
51use thiserror::Error;
52use util::stream::ConnectError;
53use util::target_addr::read_address;
54use util::target_addr::AddrError;
55use util::target_addr::TargetAddr;
56use util::target_addr::ToTargetAddr;
57
58use tokio::io::AsyncReadExt;
59
60#[rustfmt::skip]
61pub mod consts {
62 pub const SOCKS5_VERSION: u8 = 0x05;
63
64 pub const SOCKS5_AUTH_METHOD_NONE: u8 = 0x00;
65 pub const SOCKS5_AUTH_METHOD_GSSAPI: u8 = 0x01;
66 pub const SOCKS5_AUTH_METHOD_PASSWORD: u8 = 0x02;
67 pub const SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE: u8 = 0xff;
68
69 pub const SOCKS5_CMD_TCP_CONNECT: u8 = 0x01;
70 pub const SOCKS5_CMD_TCP_BIND: u8 = 0x02;
71 pub const SOCKS5_CMD_UDP_ASSOCIATE: u8 = 0x03;
72
73 pub const SOCKS5_ADDR_TYPE_IPV4: u8 = 0x01;
74 pub const SOCKS5_ADDR_TYPE_DOMAIN_NAME: u8 = 0x03;
75 pub const SOCKS5_ADDR_TYPE_IPV6: u8 = 0x04;
76
77 pub const SOCKS5_REPLY_SUCCEEDED: u8 = 0x00;
78 pub const SOCKS5_REPLY_GENERAL_FAILURE: u8 = 0x01;
79 pub const SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: u8 = 0x02;
80 pub const SOCKS5_REPLY_NETWORK_UNREACHABLE: u8 = 0x03;
81 pub const SOCKS5_REPLY_HOST_UNREACHABLE: u8 = 0x04;
82 pub const SOCKS5_REPLY_CONNECTION_REFUSED: u8 = 0x05;
83 pub const SOCKS5_REPLY_TTL_EXPIRED: u8 = 0x06;
84 pub const SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: u8 = 0x07;
85 pub const SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 0x08;
86}
87
88#[derive(Debug, PartialEq)]
89pub enum Socks5Command {
90 TCPConnect,
91 TCPBind,
92 UDPAssociate,
93}
94
95#[allow(dead_code)]
96impl Socks5Command {
97 #[inline]
98 #[rustfmt::skip]
99 fn as_u8(&self) -> u8 {
100 match self {
101 Socks5Command::TCPConnect => consts::SOCKS5_CMD_TCP_CONNECT,
102 Socks5Command::TCPBind => consts::SOCKS5_CMD_TCP_BIND,
103 Socks5Command::UDPAssociate => consts::SOCKS5_CMD_UDP_ASSOCIATE,
104 }
105 }
106
107 #[inline]
108 #[rustfmt::skip]
109 fn from_u8(code: u8) -> Option<Socks5Command> {
110 match code {
111 consts::SOCKS5_CMD_TCP_CONNECT => Some(Socks5Command::TCPConnect),
112 consts::SOCKS5_CMD_TCP_BIND => Some(Socks5Command::TCPBind),
113 consts::SOCKS5_CMD_UDP_ASSOCIATE => Some(Socks5Command::UDPAssociate),
114 _ => None,
115 }
116 }
117}
118
119#[derive(Debug, PartialEq)]
120pub enum AuthenticationMethod {
121 None,
122 Password { username: String, password: String },
123}
124
125impl AuthenticationMethod {
126 #[inline]
127 #[rustfmt::skip]
128 fn as_u8(&self) -> u8 {
129 match self {
130 AuthenticationMethod::None => consts::SOCKS5_AUTH_METHOD_NONE,
131 AuthenticationMethod::Password {..} =>
132 consts::SOCKS5_AUTH_METHOD_PASSWORD
133 }
134 }
135
136 #[inline]
137 #[rustfmt::skip]
138 fn from_u8(code: u8) -> Option<AuthenticationMethod> {
139 match code {
140 consts::SOCKS5_AUTH_METHOD_NONE => Some(AuthenticationMethod::None),
141 consts::SOCKS5_AUTH_METHOD_PASSWORD => Some(AuthenticationMethod::Password { username: "test".to_string(), password: "test".to_string()}),
142 _ => None,
143 }
144 }
145}
146
147impl fmt::Display for AuthenticationMethod {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match *self {
150 AuthenticationMethod::None => f.write_str("AuthenticationMethod::None"),
151 AuthenticationMethod::Password { .. } => f.write_str("AuthenticationMethod::Password"),
152 }
153 }
154}
155
156#[derive(Error, Debug)]
169pub enum SocksError {
170 #[error("i/o error: {0}")]
171 Io(#[from] io::Error),
172 #[error("the data for key `{0}` is not available")]
173 Redaction(String),
174 #[error("invalid header (expected {expected:?}, found {found:?})")]
175 InvalidHeader { expected: String, found: String },
176
177 #[error("Auth method unacceptable `{0:?}`.")]
178 AuthMethodUnacceptable(Vec<u8>),
179 #[error("Unsupported SOCKS version `{0}`.")]
180 UnsupportedSocksVersion(u8),
181 #[error("Domain exceeded max sequence length")]
182 ExceededMaxDomainLen(usize),
183 #[error("Authentication rejected `{0}`")]
184 AuthenticationRejected(String),
185
186 #[error(transparent)]
187 ServerError(#[from] server::SocksServerError),
188
189 #[error(transparent)]
190 UdpHeaderError(#[from] UdpHeaderError),
191
192 #[error(transparent)]
193 AddrError(#[from] AddrError),
194
195 #[error(transparent)]
196 ConnectError(#[from] ConnectError),
197
198 #[error("Error with reply: {0}.")]
199 ReplyError(#[from] ReplyError),
200
201 #[cfg(feature = "socks4")]
202 #[error("Error with reply: {0}.")]
203 ReplySocks4Error(#[from] socks4::ReplyError),
204
205 #[error("Argument input error: `{0}`.")]
206 ArgumentInputError(&'static str),
207
208 #[error(transparent)]
210 Other(#[from] anyhow::Error),
211}
212
213pub type Result<T, E = SocksError> = core::result::Result<T, E>;
214
215#[derive(Error, Debug, Copy, Clone)]
217pub enum ReplyError {
218 #[error("Succeeded")]
219 Succeeded,
220 #[error("General failure")]
221 GeneralFailure,
222 #[error("Connection not allowed by ruleset")]
223 ConnectionNotAllowed,
224 #[error("Network unreachable")]
225 NetworkUnreachable,
226 #[error("Host unreachable")]
227 HostUnreachable,
228 #[error("Connection refused")]
229 ConnectionRefused,
230 #[error("Connection timeout")]
231 ConnectionTimeout,
232 #[error("TTL expired")]
233 TtlExpired,
234 #[error("Command not supported")]
235 CommandNotSupported,
236 #[error("Address type not supported")]
237 AddressTypeNotSupported,
238 }
240
241impl ReplyError {
242 #[inline]
243 #[rustfmt::skip]
244 pub fn as_u8(self) -> u8 {
245 match self {
246 ReplyError::Succeeded => consts::SOCKS5_REPLY_SUCCEEDED,
247 ReplyError::GeneralFailure => consts::SOCKS5_REPLY_GENERAL_FAILURE,
248 ReplyError::ConnectionNotAllowed => consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED,
249 ReplyError::NetworkUnreachable => consts::SOCKS5_REPLY_NETWORK_UNREACHABLE,
250 ReplyError::HostUnreachable => consts::SOCKS5_REPLY_HOST_UNREACHABLE,
251 ReplyError::ConnectionRefused => consts::SOCKS5_REPLY_CONNECTION_REFUSED,
252 ReplyError::ConnectionTimeout => consts::SOCKS5_REPLY_TTL_EXPIRED,
253 ReplyError::TtlExpired => consts::SOCKS5_REPLY_TTL_EXPIRED,
254 ReplyError::CommandNotSupported => consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED,
255 ReplyError::AddressTypeNotSupported => consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED,
256}
258 }
259
260 #[inline]
261 #[rustfmt::skip]
262 pub fn from_u8(code: u8) -> ReplyError {
263 match code {
264 consts::SOCKS5_REPLY_SUCCEEDED => ReplyError::Succeeded,
265 consts::SOCKS5_REPLY_GENERAL_FAILURE => ReplyError::GeneralFailure,
266 consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED => ReplyError::ConnectionNotAllowed,
267 consts::SOCKS5_REPLY_NETWORK_UNREACHABLE => ReplyError::NetworkUnreachable,
268 consts::SOCKS5_REPLY_HOST_UNREACHABLE => ReplyError::HostUnreachable,
269 consts::SOCKS5_REPLY_CONNECTION_REFUSED => ReplyError::ConnectionRefused,
270 consts::SOCKS5_REPLY_TTL_EXPIRED => ReplyError::TtlExpired,
271 consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED => ReplyError::CommandNotSupported,
272 consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED => ReplyError::AddressTypeNotSupported,
273_ => unreachable!("ReplyError code unsupported."),
275 }
276 }
277}
278
279#[derive(thiserror::Error, Debug)]
280pub enum UdpHeaderError {
281 #[error(transparent)]
282 AddrError(#[from] AddrError),
283 #[error("could not convert target addr: {0}")]
284 ToTargetAddr(#[source] io::Error),
285 #[error("could not read UDP header: {0}")]
286 ReadingError(#[source] io::Error),
287 #[error("does not match the expected reserved field")]
288 GarbageInReserved,
289}
290
291pub fn new_udp_header<T: ToTargetAddr>(target_addr: T) -> Result<Vec<u8>, UdpHeaderError> {
314 let mut header = vec![
315 0, 0, 0, ];
318 header.append(
319 &mut target_addr
320 .to_target_addr()
321 .map_err(UdpHeaderError::ToTargetAddr)?
322 .to_be_bytes()?,
323 );
324
325 Ok(header)
326}
327
328pub async fn parse_udp_request<'a>(
330 mut req: &'a [u8],
331) -> Result<(u8, TargetAddr, &'a [u8]), UdpHeaderError> {
332 let rsv = read_exact!(req, [0u8; 2]).map_err(UdpHeaderError::ReadingError)?;
333
334 if !rsv.eq(&[0u8; 2]) {
335 return Err(UdpHeaderError::GarbageInReserved);
336 }
337
338 let [frag, atyp] = read_exact!(req, [0u8; 2]).map_err(UdpHeaderError::ReadingError)?;
339
340 let target_addr = read_address(&mut req, atyp).await?;
341
342 Ok((frag, target_addr, req))
343}
344
345#[cfg(test)]
346mod test {
347 use anyhow::Result;
348 use tokio::{
349 net::{TcpListener, TcpStream, UdpSocket},
350 sync::oneshot::Sender,
351 };
352
353 use crate::{client, server, ReplyError, Socks5Command};
354 use std::{
355 net::{SocketAddr, ToSocketAddrs},
356 num::ParseIntError,
357 };
358 use tokio::io::{AsyncReadExt, AsyncWriteExt};
359 use tokio::sync::oneshot;
360 use tokio_test::block_on;
361
362 fn init() {
363 let _ = env_logger::builder().is_test(true).try_init();
364 }
365
366 async fn setup_socks_server(proxy_addr: &str, tx: Sender<SocketAddr>) -> Result<()> {
367 let reply_ip = proxy_addr.parse::<SocketAddr>().unwrap().ip();
368
369 let listener = TcpListener::bind(proxy_addr).await?;
370 tx.send(listener.local_addr()?).unwrap();
371
372 loop {
373 let (stream, _) = listener.accept().await?; let proto = server::Socks5ServerProtocol::accept_no_auth(stream).await?;
375 let (proto, cmd, mut target_addr) = proto.read_command().await?;
376 target_addr = target_addr.resolve_dns().await?;
377 match cmd {
378 Socks5Command::TCPConnect => {
379 server::run_tcp_proxy(proto, &target_addr, 10, false).await?;
380 }
381 Socks5Command::UDPAssociate => {
382 server::run_udp_proxy(proto, &target_addr, None, reply_ip, None).await?;
383 }
384 Socks5Command::TCPBind => {
385 proto.reply_error(&ReplyError::CommandNotSupported).await?;
386 }
387 }
388 }
389 }
390
391 async fn google(mut socket: TcpStream) -> Result<()> {
392 socket.write_all(b"GET / HTTP/1.0\r\n\r\n").await?;
393 let mut result = vec![];
394 socket.read_to_end(&mut result).await?;
395
396 println!("{}", String::from_utf8_lossy(&result));
397 assert!(result.starts_with(b"HTTP/1.0"));
398 assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>"));
399
400 Ok(())
401 }
402
403 #[test]
404 fn google_no_auth() {
405 init();
406 block_on(async {
407 let (tx, rx) = oneshot::channel();
408 tokio::spawn(setup_socks_server("[::1]:0", tx));
409
410 let socket = client::Socks5Stream::connect(
411 rx.await.unwrap(),
412 "google.com".to_owned(),
413 80,
414 client::Config::default(),
415 )
416 .await
417 .unwrap();
418 google(socket.get_socket()).await.unwrap();
419 });
420 }
421
422 #[test]
423 fn mock_udp_assosiate_no_auth() {
424 init();
425 block_on(async {
426 const MOCK_ADDRESS: &str = "[::1]:40235";
427
428 let (tx, rx) = oneshot::channel();
429 tokio::spawn(setup_socks_server("[::1]:0", tx));
430 let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap();
431
432 let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0")
435 .await
436 .unwrap();
437 let mock_udp_server = UdpSocket::bind(MOCK_ADDRESS).await.unwrap();
438
439 tunnel
440 .send_to(
441 b"hello world!",
442 MOCK_ADDRESS.to_socket_addrs().unwrap().next().unwrap(),
443 )
444 .await
445 .unwrap();
446 println!("Send packet to {}", MOCK_ADDRESS);
447
448 let mut buf = [0; 13];
449 let (len, addr) = mock_udp_server.recv_from(&mut buf).await.unwrap();
450 assert_eq!(len, 12);
451 assert_eq!(&buf[..12], b"hello world!");
452
453 mock_udp_server
454 .send_to(b"hello world!", addr)
455 .await
456 .unwrap();
457
458 println!("Recieve packet from {}", MOCK_ADDRESS);
459 let len = tunnel.recv_from(&mut buf).await.unwrap().0;
460 assert_eq!(len, 12);
461 assert_eq!(&buf[..12], b"hello world!");
462 });
463 }
464
465 #[test]
466 fn dns_udp_assosiate_no_auth() {
467 init();
468 block_on(async {
469 const DNS_SERVER: &str = "1.1.1.1:53";
470
471 let (tx, rx) = oneshot::channel();
472 tokio::spawn(setup_socks_server("[::1]:0", tx));
473 let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap();
474
475 let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0")
478 .await
479 .unwrap();
480
481 #[rustfmt::skip]
482 tunnel.send_to(
483 &decode_hex(&(
484 "AAAA".to_owned() + "0100" + "0001" + "0000" + "0000" + "0000" + "076578616d706c65"+ "03636f6d00" + "0001" + "0001" ))
495 .unwrap(),
496 DNS_SERVER.to_socket_addrs().unwrap().next().unwrap(),
497 ).await.unwrap();
498 println!("Send packet to {}", DNS_SERVER);
499
500 let mut buf = [0; 128];
501 println!("Recieve packet from {}", DNS_SERVER);
502 tunnel.recv_from(&mut buf).await.unwrap();
503 println!("dns response {:?}", buf);
504
505 #[rustfmt::skip]
506 assert!(buf.starts_with(&decode_hex(&(
507 "AAAA".to_owned() + "8180" + "0001" )).unwrap()));
511 });
512 }
513
514 fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
515 (0..s.len())
516 .step_by(2)
517 .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
518 .collect()
519 }
520}