darkfi/net/transport/
socks5.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::{
20    fmt::Debug,
21    io,
22    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
23};
24
25use futures::{AsyncReadExt, AsyncWriteExt};
26use smol::net::TcpStream;
27use tracing::debug;
28use url::Url;
29
30/// SOCKS5 dialer
31#[derive(Clone, Debug)]
32pub struct Socks5Dialer {
33    client: Socks5Client,
34    endpoint: AddrKind,
35}
36
37impl Socks5Dialer {
38    /// Instantiate a new [`Socks5Dialer`] with given URI
39    pub(crate) async fn new(uri: &Url) -> io::Result<Self> {
40        // URIs in the form of: socks5://user:pass@proxy:port/destination:port
41        /*
42        let auth_user = uri.username();
43        let auth_pass = uri.password();
44        */
45
46        // Parse destination
47        let mut dest = uri.path().strip_prefix("/").unwrap().split(':');
48
49        let Some(dest_host) = dest.next() else { return Err(io::ErrorKind::InvalidInput.into()) };
50        let Some(dest_port) = dest.next() else { return Err(io::ErrorKind::InvalidInput.into()) };
51
52        let dest_port: u16 = match dest_port.parse() {
53            Ok(v) => v,
54            Err(_) => return Err(io::ErrorKind::InvalidData.into()),
55        };
56
57        let client = Socks5Client::new(uri.host_str().unwrap(), uri.port().unwrap());
58        let endpoint: AddrKind = (dest_host, dest_port).into();
59
60        Ok(Self { client, endpoint })
61    }
62
63    /// Internal dial function
64    pub(crate) async fn do_dial(&self) -> io::Result<TcpStream> {
65        debug!(
66            target: "net::socks5::do_dial",
67            "Dialing {:?} with SOCKS5...", self.endpoint,
68        );
69
70        self.client.connect(self.endpoint.clone()).await
71    }
72}
73
74/// SOCKS5 proxy client
75#[derive(Clone, Debug)]
76pub struct Socks5Client {
77    /// SOCKS5 server host
78    host: String,
79    /// SOCKS5 server port
80    port: u16,
81}
82
83impl Socks5Client {
84    /// Instantiate a new SOCKS5 client from given host and port
85    pub fn new(host: &str, port: u16) -> Self {
86        Self { host: String::from(host), port }
87    }
88
89    /// Connect an instantiated SOCKS5 client to the given destination
90    pub async fn connect(&self, addr: impl Into<AddrKind> + Debug) -> io::Result<TcpStream> {
91        let addr: AddrKind = addr.into();
92
93        // Connect to the SOCKS proxy
94        let mut stream = TcpStream::connect(&format!("{}:{}", self.host, self.port)).await?;
95
96        // Send version identifier/method selection message
97        // VER=5, NMETHODS=1, METHOD=NO_AUTH
98        stream.write_all(&[0x05, 0x01, 0x00]).await?;
99        stream.flush().await?;
100
101        // Read server method selection message
102        let mut buf = [0u8; 2];
103        stream.read_exact(&mut buf).await?;
104
105        // Currently we will only support METHOD=NO_AUTH (0x00)
106        if buf[0] != 0x05 && buf[0] != 0x00 {
107            return Err(io::ErrorKind::ConnectionRefused.into())
108        }
109
110        // Build CONNECT request
111
112        // VER=5, CMD=CONNECT, RSV
113        let mut reqbuf = vec![0x05, 0x01, 0x00];
114
115        match addr {
116            AddrKind::Ip(socketaddr) => {
117                if socketaddr.is_ipv4() {
118                    // ATYP=0x01
119                    reqbuf.push(0x01);
120                } else {
121                    // ATYP=0x04
122                    reqbuf.push(0x04);
123                }
124                // DST.ADDR
125                match socketaddr.ip() {
126                    IpAddr::V4(ip) => reqbuf.extend_from_slice(&ip.octets()),
127                    IpAddr::V6(ip) => reqbuf.extend_from_slice(&ip.octets()),
128                }
129                // DST.PORT
130                reqbuf.extend_from_slice(&socketaddr.port().to_be_bytes());
131            }
132            AddrKind::Domain(ref host, port) => {
133                // ATYP=0x03
134                reqbuf.push(0x03);
135                // DST.ADDR
136                reqbuf.push(host.len() as u8);
137                reqbuf.extend_from_slice(host.as_bytes());
138                // DST.PORT
139                reqbuf.extend_from_slice(&port.to_be_bytes());
140            }
141        };
142
143        // Send it
144        stream.write_all(&reqbuf).await?;
145        stream.flush().await?;
146        debug!(
147            target: "net::transport::socks5::connect",
148            "Flushed CONNECT({addr:?}) request"
149        );
150
151        // Handle the SOCKS server reply
152        let mut buf = [0u8];
153        stream.read_exact(&mut buf).await?;
154        debug!(
155            target: "net::transport::socks5::connect",
156            "REPLY - Version: {:#02x}", buf[0],
157        );
158
159        if buf[0] != 0x05 {
160            return Err(io::ErrorKind::ConnectionRefused.into())
161        }
162
163        buf[0] = 0x00;
164        stream.read_exact(&mut buf).await?;
165        debug!(
166            target: "net::transport::socks5::connect",
167            "REPLY - Reply: {:#02x}", buf[0],
168        );
169        match buf[0] {
170            0x00 => {}
171            0x01 => return Err(io::ErrorKind::ConnectionAborted.into()),
172            0x02 => return Err(io::ErrorKind::PermissionDenied.into()),
173            0x03 => return Err(io::ErrorKind::NetworkUnreachable.into()),
174            0x04 => return Err(io::ErrorKind::HostUnreachable.into()),
175            0x05 => return Err(io::ErrorKind::ConnectionRefused.into()),
176            0x06 => return Err(io::ErrorKind::TimedOut.into()),
177            0x07 => return Err(io::ErrorKind::Unsupported.into()),
178            0x08 => return Err(io::ErrorKind::Unsupported.into()),
179            _ => return Err(io::ErrorKind::ConnectionAborted.into()),
180        }
181
182        // Read RSV
183        stream.read_exact(&mut buf).await?;
184
185        // Read ATYP
186        buf[0] = 0x00;
187        stream.read_exact(&mut buf).await?;
188        debug!(
189            target: "net::transport::socks5::connect",
190            "REPLY - ATYP: {:#02x}", buf[0],
191        );
192
193        // Read BND.ADDR accordingly
194        match buf[0] {
195            // IPv4
196            0x01 => {
197                let mut buf = [0u8; 4];
198                stream.read_exact(&mut buf).await?;
199                debug!(
200                    target: "net::transport::socks5::connect",
201                    "REPLY - BND.ADDR: {}", Ipv4Addr::from(buf),
202                );
203            }
204            // IPv6
205            0x04 => {
206                let mut buf = [0u8; 16];
207                stream.read_exact(&mut buf).await?;
208                debug!(
209                    target: "net::transport::socks5::connect",
210                    "REPLY - BND.ADDR: {}", Ipv6Addr::from(buf),
211                );
212            }
213            // Domain
214            0x03 => {
215                let mut len = [0u8];
216                stream.read_exact(&mut len).await?;
217                let mut buf = vec![0u8; len[0] as usize];
218                stream.read_exact(&mut buf).await?;
219                debug!(
220                    target: "net::transport::socks5::connect",
221                    "REPLY - BND.ADDR: {}", String::from_utf8_lossy(&buf),
222                );
223            }
224
225            _ => return Err(io::ErrorKind::ConnectionAborted.into()),
226        };
227
228        // Read BND.PORT
229        let mut buf = [0u8; 2];
230        stream.read_exact(&mut buf).await?;
231        debug!(
232            target: "net::transport::socks5::connect",
233            "REPLY - BND.PORT: {}", u16::from_be_bytes(buf),
234        );
235
236        Ok(stream)
237    }
238}
239
240#[derive(Clone, Debug)]
241pub enum AddrKind {
242    Ip(SocketAddr),
243    Domain(String, u16),
244}
245
246impl From<(IpAddr, u16)> for AddrKind {
247    fn from(value: (IpAddr, u16)) -> Self {
248        Self::Ip(value.into())
249    }
250}
251
252impl From<(Ipv4Addr, u16)> for AddrKind {
253    fn from(value: (Ipv4Addr, u16)) -> Self {
254        Self::Ip(value.into())
255    }
256}
257
258impl From<(Ipv6Addr, u16)> for AddrKind {
259    fn from(value: (Ipv6Addr, u16)) -> Self {
260        Self::Ip(value.into())
261    }
262}
263
264impl From<(String, u16)> for AddrKind {
265    fn from((domain, port): (String, u16)) -> Self {
266        Self::Domain(domain, port)
267    }
268}
269
270impl From<(&'_ str, u16)> for AddrKind {
271    fn from((domain, port): (&'_ str, u16)) -> Self {
272        Self::Domain(domain.to_owned(), port)
273    }
274}
275
276impl From<SocketAddr> for AddrKind {
277    fn from(value: SocketAddr) -> Self {
278        Self::Ip(value)
279    }
280}
281
282impl From<SocketAddrV4> for AddrKind {
283    fn from(value: SocketAddrV4) -> Self {
284        Self::Ip(value.into())
285    }
286}
287
288impl From<SocketAddrV6> for AddrKind {
289    fn from(value: SocketAddrV6) -> Self {
290        Self::Ip(value.into())
291    }
292}