socksx/socks6/
s6_client.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::{convert::TryInto, net::SocketAddr};

use anyhow::{ensure, Result};
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;

use crate::{Address, constants::*, Credentials};
use crate::socks6::{self, Socks6Request};
use crate::socks6::{
    AuthMethod,
    options::{AuthMethodAdvertisementOption, SocksOption},
};

/// Represents a SOCKS6 client.
#[derive(Clone)]
pub struct Socks6Client {
    proxy_addr: SocketAddr,
    credentials: Option<Credentials>,
}

impl Socks6Client {
    /// Creates a new Socks6Client.
    ///
    /// # Parameters
    /// - `proxy_addr`: The address of the SOCKS6 proxy.
    /// - `credentials`: Optional credentials for authentication.
    ///
    /// # Returns
    /// A `Result` containing a new `Socks6Client` or an error.
    pub async fn new<A: Into<String>>(
        proxy_addr: A,
        credentials: Option<Credentials>,
    ) -> Result<Self> {
        let proxy_addr = crate::resolve_addr(proxy_addr).await?;

        Ok(Socks6Client {
            proxy_addr,
            credentials,
        })
    }

    /// Connects to a given destination through the SOCKS6 proxy.
    ///
    /// # Parameters
    /// - `destination`: The destination to connect to.
    /// - `initial_data`: Optional initial data to send.
    /// - `options`: Optional SOCKS options.
    ///
    /// # Returns
    /// A `Result` containing a tuple of the `TcpStream` and the bound `Address`, or an error.
    pub async fn connect<A>(
        &self,
        destination: A,
        initial_data: Option<Vec<u8>>,
        options: Option<Vec<SocksOption>>,
    ) -> Result<(TcpStream, Address)>
    where
        A: TryInto<Address, Error = anyhow::Error>,
    {
        let mut stream = TcpStream::connect(&self.proxy_addr).await?;
        let binding = self.handshake(destination, initial_data, options, &mut stream).await?;

        Ok((stream, binding))
    }

    /// Conducts the handshake process with the SOCKS6 proxy.
    ///
    /// This method implements the handshake protocol as per [socks6-draft11].
    /// [socks6-draft11]: https://tools.ietf.org/html/draft-olteanu-intarea-socks-6-11
    ///
    /// # Parameters
    /// - `destination`: The destination to connect to.
    /// - `initial_data`: Optional initial data to send.
    /// - `options`: Optional SOCKS options.
    /// - `stream`: The mutable reference to the `TcpStream`.
    ///
    /// # Returns
    /// A `Result` containing the bound `Address` or an error.
    pub async fn handshake<A>(
        &self,
        destination: A,
        initial_data: Option<Vec<u8>>,
        options: Option<Vec<SocksOption>>,
        stream: &mut TcpStream,
    ) -> Result<Address>
    where
        A: TryInto<Address, Error = anyhow::Error>,
    {
        if let Some(Credentials { username, password }) = &self.credentials {
            ensure!(username.len() > 255, "Username MUST NOT be larger than 255 bytes.");
            ensure!(password.len() > 255, "Password MUST NOT be larger than 255 bytes.");
        }

        // Prepare initial data.
        let initial_data = initial_data.unwrap_or_default();
        ensure!(
            initial_data.len() <= 2 ^ 14,
            "Initial data MUST NOT be larger than 16384 bytes."
        );
        let initial_data_length = initial_data.len() as u16;

        // Prepare SOCKS options.
        let mut auth_methods = vec![];
        if self.credentials.is_some() {
            auth_methods.push(AuthMethod::UsernamePassword);
        }

        let auth_methods_adv = AuthMethodAdvertisementOption::new(initial_data_length, vec![]);
        let mut options = options.unwrap_or_default();
        options.push(auth_methods_adv.wrap());

        // Create SOCKS6 CONNECT request.
        let request = Socks6Request::new(
            SOCKS_CMD_CONNECT,
            destination.try_into()?,
            initial_data_length,
            options,
            None,
        );

        // Send SOCKS request information.
        let request_bytes = request.into_socks_bytes();
        stream.write(&request_bytes).await?;

        // Wait for authentication and operation reply.
        let _ = socks6::read_no_authentication(stream).await?;
        let (binding, _) = socks6::read_reply(stream).await?;

        Ok(binding)
    }
}