fast_socks5/
lib.rs

1//! Fast SOCKS5 client/server implementation written in Rust async/.await (with tokio).
2//!
3//! This library is maintained by [anyip.io](https://anyip.io/) a residential and mobile socks5 proxy provider.
4//!
5//! ## Features
6//!
7//! - An `async`/`.await` [SOCKS5](https://tools.ietf.org/html/rfc1928) implementation.
8//! - An `async`/`.await` [SOCKS4 Client](https://www.openssh.com/txt/socks4.protocol) implementation.
9//! - An `async`/`.await` [SOCKS4a Client](https://www.openssh.com/txt/socks4a.protocol) implementation.
10//! - No **unsafe** code
11//! - Built on-top of `tokio` library
12//! - Ultra lightweight and scalable
13//! - No system dependencies
14//! - Cross-platform
15//! - Authentication methods:
16//!   - No-Auth method
17//!   - Username/Password auth method
18//!   - Custom auth methods can be implemented via the Authentication Trait
19//!   - Credentials returned on authentication success
20//! - All SOCKS5 RFC errors (replies) should be mapped
21//! - `AsyncRead + AsyncWrite` traits are implemented on Socks5Stream & Socks5Socket
22//! - `IPv4`, `IPv6`, and `Domains` types are supported
23//! - Config helper for Socks5Server
24//! - Helpers to run a Socks5Server à la *"std's TcpStream"* via `incoming.next().await`
25//! - Examples come with real cases commands scenarios
26//! - Can disable `DNS resolving`
27//! - Can skip the authentication/handshake process, which will directly handle command's request (useful to save useless round-trips in a current authenticated environment)
28//! - Can disable command execution (useful if you just want to forward the request to a different server)
29//!
30//!
31//! ## Install
32//!
33//! Open in [crates.io](https://crates.io/crates/fast-socks5).
34//!
35//!
36//! ## Examples
37//!
38//! Please check [`examples`](https://github.com/dizda/fast-socks5/tree/master/examples) directory.
39
40#![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//impl Vec<AuthenticationMethod> {
158//    pub fn as_bytes(&self) -> &[u8] {
159//        self.iter().map(|l| l.as_u8()).collect()
160//    }
161//}
162//
163//impl From<&[AuthenticationMethod]> for &[u8] {
164//    fn from(_: Vec<AuthenticationMethod>) -> Self {
165//        &[0x00]
166//    }
167//}
168
169#[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("Other: `{0}`.")]
200    #[error(transparent)]
201    Other(#[from] anyhow::Error),
202}
203
204pub type Result<T, E = SocksError> = core::result::Result<T, E>;
205
206/// SOCKS5 reply code
207#[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    //    OtherReply(u8),
230}
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//            ReplyError::OtherReply(c)           => c,
248        }
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//            _                                               => ReplyError::OtherReply(code),
265            _                                               => unreachable!("ReplyError code unsupported."),
266        }
267    }
268}
269
270/// Generate UDP header
271///
272/// # UDP Request header structure.
273/// ```text
274/// +----+------+------+----------+----------+----------+
275/// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
276/// +----+------+------+----------+----------+----------+
277/// | 2  |  1   |  1   | Variable |    2     | Variable |
278/// +----+------+------+----------+----------+----------+
279///
280/// The fields in the UDP request header are:
281///
282///     o  RSV  Reserved X'0000'
283///     o  FRAG    Current fragment number
284///     o  ATYP    address type of following addresses:
285///        o  IP V4 address: X'01'
286///        o  DOMAINNAME: X'03'
287///        o  IP V6 address: X'04'
288///     o  DST.ADDR       desired destination address
289///     o  DST.PORT       desired destination port
290///     o  DATA     user data
291/// ```
292pub fn new_udp_header<T: ToTargetAddr>(target_addr: T) -> Result<Vec<u8>> {
293    let mut header = vec![
294        0, 0, // RSV
295        0, // FRAG
296    ];
297    header.append(&mut target_addr.to_target_addr()?.to_be_bytes()?);
298
299    Ok(header)
300}
301
302/// Parse data from UDP client on raw buffer, return (frag, target_addr, payload).
303pub 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        // print explicit error
314        error!("{:#}", e);
315        // then convert it to a reply
316        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            // Creates a UDP tunnel which can be used to forward UDP packets, "[::]:0" indicates the
413            // binding source address used to communicate with the socks5 server.
414            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            // Creates a UDP tunnel which can be used to forward UDP packets, "[::]:0" indicates the
456            // binding source address used to communicate with the socks5 server.
457            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()   // ID
465                    + "0100"            // Query parameters
466                    + "0001"            // Number of questions
467                    + "0000"            // Number of answers
468                    + "0000"            // Number of authority records
469                    + "0000"            // Number of additional records
470                    + "076578616d706c65"// Length + hex("example")
471                    + "03636f6d00"      // Length + hex("com") + zero byte
472                    + "0001"            // QTYPE
473                    + "0001"            // QCLASS
474                ))
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()   // ID
488                + "8180"            // FLAGS: RCODE=0, No errors reported
489                + "0001"            // One question
490            )).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}