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