socksx/socks5/
s5_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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use std::convert::TryInto;
use std::net::SocketAddr;

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

use crate::{Address, constants::*, Credentials};
use crate::socks5::{self, Socks5Request};

/// Represents a SOCKS5 client for connecting to proxy servers.
#[derive(Clone)]
pub struct Socks5Client {
    proxy_addr: SocketAddr,
    credentials: Option<Credentials>,
}

impl Socks5Client {
    /// Creates a new `Socks5Client`.
    ///
    /// # Arguments
    ///
    /// * `proxy_addr` - The address of the SOCKS5 proxy server.
    /// * `credentials` - Optional SOCKS5 authentication credentials.
    ///
    /// # Returns
    ///
    /// A `Result` containing the new `Socks5Client` instance.
    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(Socks5Client {
            proxy_addr,
            credentials,
        })
    }

    /// Establishes a SOCKS5 connection to the specified destination.
    ///
    /// # Arguments
    ///
    /// * `destination` - The target address and port to connect to.
    ///
    /// # Returns
    ///
    /// A `Result` containing a tuple with a `TcpStream` to the destination and the bound address.
    pub async fn connect<A>(
        &self,
        destination: A,
    ) -> Result<(TcpStream, 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.");
        }

        // Create SOCKS5 CONNECT request.
        let request = Socks5Request::new(SOCKS_CMD_CONNECT, destination.try_into()?);

        let mut stream = TcpStream::connect(&self.proxy_addr).await?;

        // Enter authentication negotiation.
        let auth_method = self.negotiate_auth_method(&mut stream).await?;
        if auth_method == SOCKS_AUTH_USERNAME_PASSWORD {
            if let Some(credentials) = &self.credentials {
                self.authenticate(&mut stream, credentials).await?;
            } else {
                unreachable!();
            }
        }

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

        // Read operation reply.
        let binding = socks5::read_reply(&mut stream).await?;

        Ok((stream, binding))
    }

    /// Negotiates the SOCKS5 authentication method with the proxy server.
    ///
    /// # Arguments
    ///
    /// * `stream` - The TCP stream connected to the proxy server.
    ///
    /// # Returns
    ///
    /// A `Result` containing the selected authentication method.
    async fn negotiate_auth_method(
        &self,
        stream: &mut TcpStream,
    ) -> Result<u8> {
        let mut request = vec![SOCKS_VER_5, 0x01, SOCKS_AUTH_NOT_REQUIRED];
        if self.credentials.is_some() {
            request[1] = 0x02;
            request.push(SOCKS_AUTH_USERNAME_PASSWORD);
        }

        stream.write(&request).await?;

        let mut reply = [0; 2];
        stream.read_exact(&mut reply).await?;

        let socks_version = reply[0];
        if socks_version != SOCKS_VER_5 {
            bail!("Proxy uses a different SOCKS version: {}.", socks_version);
        }

        let auth_method = reply[1];
        match auth_method {
            0x00 => Ok(auth_method),
            0x02 => {
                if self.credentials.is_none() {
                    bail!("Proxy demands authentication, but no credentials are provided.");
                } else {
                    Ok(auth_method)
                }
            }
            0xFF => bail!("Proxy did not accept authentication method."),
            _ => bail!("Proxy proposed unsupported authentication method: {}.", auth_method),
        }
    }

    /// Authenticates with the SOCKS5 proxy using the provided credentials.
    ///
    /// # Arguments
    ///
    /// * `stream` - The TCP stream connected to the proxy server.
    /// * `credentials` - The authentication credentials.
    ///
    /// # Returns
    ///
    /// A `Result` indicating success or an error if authentication fails.
    async fn authenticate(
        &self,
        stream: &mut TcpStream,
        credentials: &Credentials,
    ) -> Result<()> {
        let mut request = vec![SOCKS_AUTH_VER];
        request.extend(credentials.as_socks_bytes());

        stream.write(&request).await?;

        let mut reply = [0; 2];
        stream.read_exact(&mut reply).await?;

        let auth_version = reply[0];
        if auth_version != SOCKS_AUTH_VER {
            bail!(
                "Proxy uses a different authentication method version: {}.",
                auth_version
            );
        }

        // Check if status indicates success. If not, bail to close the connection.
        let status = reply[1];
        if status != SOCKS_AUTH_SUCCESS {
            bail!("Authentication with the provided credentials failed.");
        }

        Ok(())
    }
}