Skip to main content

karyon_net/layers/proxy/
socks5.rs

1use std::net::{Ipv4Addr, Ipv6Addr};
2
3use karyon_core::async_runtime::io::{AsyncReadExt, AsyncWriteExt};
4
5use crate::{layer::ClientLayer, ByteStream, Error, Result};
6
7const SOCKS5_VERSION: u8 = 0x05;
8const NO_AUTH: u8 = 0x00;
9const CMD_CONNECT: u8 = 0x01;
10const ATYPE_IPV4: u8 = 0x01;
11const ATYPE_DOMAIN: u8 = 0x03;
12const ATYPE_IPV6: u8 = 0x04;
13const REPLY_SUCCESS: u8 = 0x00;
14
15/// SOCKS5 proxy layer (RFC 1928). Client-only.
16///
17/// Performs the SOCKS5 handshake on the stream (which is already
18/// connected to the proxy) to tunnel through to the target.
19///
20/// # Example
21///
22/// ```no_run
23/// use karyon_net::{tcp, ClientLayer, Endpoint};
24/// use karyon_net::layers::proxy::Socks5Layer;
25///
26/// async {
27///     // Connect to the proxy
28///     let proxy_ep: Endpoint = "tcp://127.0.0.1:1080".parse().unwrap();
29///     let stream = tcp::connect(&proxy_ep, Default::default()).await.unwrap();
30///
31///     // Tunnel to target via SOCKS5
32///     let layer = Socks5Layer::new("example.com", 443);
33///     let tunneled = ClientLayer::handshake(&layer, stream).await.unwrap();
34/// };
35/// ```
36#[derive(Clone)]
37pub struct Socks5Layer {
38    target_host: String,
39    target_port: u16,
40}
41
42impl Socks5Layer {
43    /// Create a new SOCKS5 layer targeting the given host and port.
44    pub fn new(host: &str, port: u16) -> Self {
45        Self {
46            target_host: host.to_string(),
47            target_port: port,
48        }
49    }
50}
51
52impl ClientLayer<Box<dyn ByteStream>, Box<dyn ByteStream>> for Socks5Layer {
53    async fn handshake(&self, mut stream: Box<dyn ByteStream>) -> Result<Box<dyn ByteStream>> {
54        // Greeting: version + 1 method (no auth).
55        stream.write_all(&[SOCKS5_VERSION, 1, NO_AUTH]).await?;
56        stream.flush().await?;
57
58        // Server picks a method.
59        let mut resp = [0u8; 2];
60        stream.read_exact(&mut resp).await?;
61        if resp[0] != SOCKS5_VERSION || resp[1] != NO_AUTH {
62            return Err(Error::Socks5("server rejected auth method".into()));
63        }
64
65        // Build connect request.
66        let host = self.target_host.as_bytes();
67        let port = self.target_port.to_be_bytes();
68
69        let mut req = vec![SOCKS5_VERSION, CMD_CONNECT, 0x00];
70        if let Ok(ip) = self.target_host.parse::<Ipv4Addr>() {
71            req.push(ATYPE_IPV4);
72            req.extend_from_slice(&ip.octets());
73        } else if let Ok(ip) = self.target_host.parse::<Ipv6Addr>() {
74            req.push(ATYPE_IPV6);
75            req.extend_from_slice(&ip.octets());
76        } else {
77            req.push(ATYPE_DOMAIN);
78            req.push(host.len() as u8);
79            req.extend_from_slice(host);
80        }
81        req.extend_from_slice(&port);
82
83        stream.write_all(&req).await?;
84        stream.flush().await?;
85
86        // Read reply header (4 bytes minimum).
87        let mut reply = [0u8; 4];
88        stream.read_exact(&mut reply).await?;
89        if reply[0] != SOCKS5_VERSION {
90            return Err(Error::Socks5("invalid reply version".into()));
91        }
92        if reply[1] != REPLY_SUCCESS {
93            return Err(Error::Socks5(format!("connect failed (code {})", reply[1])));
94        }
95
96        // Skip the bound address in the reply.
97        match reply[3] {
98            ATYPE_IPV4 => {
99                let mut skip = [0u8; 4 + 2]; // 4 ip + 2 port
100                stream.read_exact(&mut skip).await?;
101            }
102            ATYPE_DOMAIN => {
103                let mut len = [0u8; 1];
104                stream.read_exact(&mut len).await?;
105                let mut skip = vec![0u8; len[0] as usize + 2];
106                stream.read_exact(&mut skip).await?;
107            }
108            ATYPE_IPV6 => {
109                let mut skip = [0u8; 16 + 2];
110                stream.read_exact(&mut skip).await?;
111            }
112            atype => {
113                return Err(Error::Socks5(format!(
114                    "unknown address type in reply: {atype:#x}"
115                )));
116            }
117        }
118
119        Ok(stream)
120    }
121}