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 the [Tokio](https://tokio.rs/) runtime
12//! - Ultra lightweight and scalable
13//! - No system dependencies
14//! - Cross-platform
15//! - Infinitely extensible, explicit server API based on typestates for safety
16//!   - You control the request handling, the library only ensures you follow the proper protocol flow
17//!   - Can skip DNS resolution
18//!   - Can skip the authentication/handshake process (not RFC-compliant, for private use, to save on useless round-trips)
19//!   - Instead of proxying in-process, swap out `run_tcp_proxy` for custom handling to build a router or to use a custom accelerated proxying method
20//! - Authentication methods:
21//!   - No-Auth method (`0x00`)
22//!   - Username/Password auth method (`0x02`)
23//!   - Custom auth methods can be implemented on the server side via the `AuthMethod` Trait
24//!     - Multiple auth methods with runtime negotiation can be supported, with fast *static* dispatch (enums can be generated with the `auth_method_enums` macro)
25//! - UDP is supported
26//! - All SOCKS5 RFC errors (replies) should be mapped
27//! - `IPv4`, `IPv6`, and `Domains` types are supported
28//!
29//! ## Install
30//!
31//! Open in [crates.io](https://crates.io/crates/fast-socks5).
32//!
33//!
34//! ## Examples
35//!
36//! Please check [`examples`](https://github.com/dizda/fast-socks5/tree/master/examples) directory.
37
38#![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//impl Vec<AuthenticationMethod> {
157//    pub fn as_bytes(&self) -> &[u8] {
158//        self.iter().map(|l| l.as_u8()).collect()
159//    }
160//}
161//
162//impl From<&[AuthenticationMethod]> for &[u8] {
163//    fn from(_: Vec<AuthenticationMethod>) -> Self {
164//        &[0x00]
165//    }
166//}
167
168#[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("Other: `{0}`.")]
209    #[error(transparent)]
210    Other(#[from] anyhow::Error),
211}
212
213pub type Result<T, E = SocksError> = core::result::Result<T, E>;
214
215/// SOCKS5 reply code
216#[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    //    OtherReply(u8),
239}
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//            ReplyError::OtherReply(c)           => c,
257        }
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//            _                                               => ReplyError::OtherReply(code),
274            _                                               => 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
291/// Generate UDP header
292///
293/// # UDP Request header structure.
294/// ```text
295/// +----+------+------+----------+----------+----------+
296/// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
297/// +----+------+------+----------+----------+----------+
298/// | 2  |  1   |  1   | Variable |    2     | Variable |
299/// +----+------+------+----------+----------+----------+
300///
301/// The fields in the UDP request header are:
302///
303///     o  RSV  Reserved X'0000'
304///     o  FRAG    Current fragment number
305///     o  ATYP    address type of following addresses:
306///        o  IP V4 address: X'01'
307///        o  DOMAINNAME: X'03'
308///        o  IP V6 address: X'04'
309///     o  DST.ADDR       desired destination address
310///     o  DST.PORT       desired destination port
311///     o  DATA     user data
312/// ```
313pub fn new_udp_header<T: ToTargetAddr>(target_addr: T) -> Result<Vec<u8>, UdpHeaderError> {
314    let mut header = vec![
315        0, 0, // RSV
316        0, // FRAG
317    ];
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
328/// Parse data from UDP client on raw buffer, return (frag, target_addr, payload).
329pub 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?; // NOTE: not spawning for test
374            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            // Creates a UDP tunnel which can be used to forward UDP packets, "[::]:0" indicates the
433            // binding source address used to communicate with the socks5 server.
434            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            // Creates a UDP tunnel which can be used to forward UDP packets, "[::]:0" indicates the
476            // binding source address used to communicate with the socks5 server.
477            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()   // ID
485                    + "0100"            // Query parameters
486                    + "0001"            // Number of questions
487                    + "0000"            // Number of answers
488                    + "0000"            // Number of authority records
489                    + "0000"            // Number of additional records
490                    + "076578616d706c65"// Length + hex("example")
491                    + "03636f6d00"      // Length + hex("com") + zero byte
492                    + "0001"            // QTYPE
493                    + "0001"            // QCLASS
494                ))
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()   // ID
508                + "8180"            // FLAGS: RCODE=0, No errors reported
509                + "0001"            // One question
510            )).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}