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}