async_proxy/clients/
socks5.rs

1use byteorder::{ByteOrder, BigEndian};
2use std::borrow::Cow;
3use std::str::FromStr;
4use std::net;
5
6/// Module contains implementation of
7/// the socks5 proxification protocol
8/// with no authentification required
9/// when establishing a connection
10/// between a client and a socks5 server
11pub mod no_auth;
12
13pub use no_auth::TcpNoAuth;
14
15/// The Socks5 protocol command representation
16#[repr(C)]
17pub enum Command {
18    TcpConnectionEstablishment = 1,
19    TcpPortBinding,
20    UdpPortBinding
21}
22
23/// Represents a destination address of
24/// a service, to which a user wants to
25/// connect through a socks5 proxy.
26/// It is a good solution, but not
27/// the fastest, so it will be rewritten in the
28/// future in preference to a dispatch mechanism
29pub enum Destination {
30    /// Represents an IPv4 address
31    Ipv4Addr(std::net::Ipv4Addr),
32    /// Represents a domain name
33    DomainName(Cow<'static, str>),
34    /// Represents an IPv6 address
35    Ipv6Addr(std::net::Ipv6Addr)
36}
37
38impl Destination {
39    /// Returns the length in bytes 
40    /// of the destination, represented as a buffer
41    pub fn len_as_buffer(&self) -> usize {
42        match self {
43            Destination::Ipv4Addr(_) => 4 + 1,
44            Destination::DomainName(name) => name.len() + 2,
45            Destination::Ipv6Addr(_) => 16 + 1
46        }
47    }
48
49    /// Extends buffer with a buffer
50    /// representation of a Destination
51    /// (See the Socks5 wiki for more information).
52    ///
53    /// Note:
54    ///     I wanted to make this function generic, such as
55    ///     it would have the signature like that:
56    ///     ```
57    ///     fn extend_buffer(buf: impl AsMut<u8>)
58    ///     ```
59    ///     but it was preffered to don't to it, because the sense of this
60    ///     flexibility will lead to longer compilation time, and that
61    ///     is totally okay in most of cases, but the function is not even
62    ///     `pub(crate)`, so the choice is obvious
63    ///
64    fn extend_buffer(&mut self, buf: &mut [u8])
65        -> Result<(), ()>
66    {
67        match self {
68            Destination::Ipv4Addr(addr) => {
69                // If the destination is an IPv4 address, then
70                // the first byte of the buffer will
71                // contain `0x01`
72                buf[0] = 0x01;
73
74                // Then we need represent the IPv4
75                // address as a buffer (in the network byte order)
76                // and copy it to our buffer `buf`
77                BigEndian::write_u32(&mut buf[1..5], addr.clone().into());
78            },
79            Destination::DomainName(name) => {
80                // If the destination is a domain name, then
81                // the first byte of the buffer will
82                // contain `0x03`
83                buf[0] = 0x03;
84
85                // Then we need to compute the length
86                // of the domain name and store it
87                // as a next byte in the buffer.
88                // The length cannot be larger than
89                // the maxumim value of a byte (0xFF or 255),
90                // so we need to make sure of it
91
92                if name.len() > 255 {
93                    return Err(())
94                }
95
96                // Storing the length
97                buf[1] = name.len() as u8;
98
99                // Then the socks5 protocol requires us to 
100                // represent the domain name address as
101                // a buffer and copy it to our buffer `buf`
102                buf[2..].clone_from_slice(name.as_bytes());
103            },
104            Destination::Ipv6Addr(addr) => {
105                // If the destination is an IPv6 address, then
106                // the first byte of the buffer will
107                // contain `0x04`
108                buf[0] = 0x04;
109
110                // Then we need represent the IPv4
111                // address as a buffer (in the network byte order)
112                // and copy it to our buffer `buf`
113                BigEndian::write_u128(&mut buf[1..17], addr.clone().into());
114            }
115        }
116
117        Ok(())
118    }
119}
120
121impl FromStr for Destination {
122    type Err = ();
123
124    /// Parses a socks5 destination.
125    /// The parsing algorithm is simpler than
126    /// you can think of:
127    ///     At first, it tries to parse an IPv4
128    ///     from the string.
129    ///     If succeed, returns an IPv4 destination representation,
130    ///     If not, it tries to parse an IPv6
131    ///     from the string.
132    ///     Then, if succeed, returns an IPv6 destination representation.
133    ///     Finally, if not, it tries to parse a domain name
134    ///     from the string and returns a domain name destination
135    ///     if succeed, unless `Err`
136    fn from_str(s: &str) -> Result<Destination, Self::Err> {
137        // Trying to parse an ipv4 address from the string
138        // (Actually, not the best code, but better that
139        //  multiple calls of `.map` or nested matched for ex.)
140        let result = s.parse::<net::Ipv4Addr>();
141        if result.is_ok() { 
142            return Ok(Destination::Ipv4Addr(result.unwrap()))
143        }
144       
145        // Trying to parse an IPv6 address from the string
146        let result = s.parse::<net::Ipv6Addr>();
147        if result.is_ok() { 
148            return Ok(Destination::Ipv6Addr(result.unwrap()))
149        }
150
151        // Trying to parse a domain name
152        webpki::DNSNameRef::try_from_ascii_str(s)
153                           .map_err(|_| ())?;
154
155        Ok(Destination::DomainName(Cow::Owned(s.to_owned())))
156    }
157}