Skip to main content

proxychains_masq/proxy/
socks4.rs

1use anyhow::{bail, Context, Result};
2use tokio::io::{AsyncReadExt, AsyncWriteExt};
3
4use super::{BoxStream, Target};
5
6/// Perform a SOCKS4 / SOCKS4a CONNECT handshake on an already-connected `stream`.
7///
8/// If `target` is [`Target::Host`], SOCKS4a extension is used (fake IP `0.0.0.1`,
9/// hostname appended after the username field).
10///
11/// # Errors
12///
13/// Returns an error if the handshake fails or the proxy reports a non-success status.
14pub async fn connect(
15    mut stream: BoxStream,
16    target: &Target,
17    username: Option<&str>,
18) -> Result<BoxStream> {
19    let port = target.port();
20    let user_bytes = username.unwrap_or("").as_bytes();
21
22    // ── Build request ─────────────────────────────────────────────────────────
23    let mut req: Vec<u8> = Vec::with_capacity(32);
24    req.push(4); // version
25    req.push(1); // CONNECT
26
27    // Destination port (big-endian)
28    req.extend_from_slice(&port.to_be_bytes());
29
30    match target {
31        Target::Ip(addr, _) => {
32            match addr {
33                std::net::IpAddr::V4(v4) => req.extend_from_slice(&v4.octets()),
34                std::net::IpAddr::V6(_) => bail!("SOCKS4 does not support IPv6 addresses"),
35            }
36            req.extend_from_slice(user_bytes);
37            req.push(0); // null-terminate username
38        }
39        Target::Host(hostname, _) => {
40            // SOCKS4a: fake IP 0.0.0.1
41            req.extend_from_slice(&[0, 0, 0, 1]);
42            req.extend_from_slice(user_bytes);
43            req.push(0); // null-terminate username
44            req.extend_from_slice(hostname.as_bytes());
45            req.push(0); // null-terminate hostname
46        }
47    }
48
49    stream
50        .write_all(&req)
51        .await
52        .context("socks4: write request")?;
53
54    // ── Read response (8 bytes) ────────────────────────────────────────────────
55    let mut resp = [0u8; 8];
56    stream
57        .read_exact(&mut resp)
58        .await
59        .context("socks4: read response")?;
60
61    // Byte 0 must be 0 (null), byte 1 must be 90 (0x5A = granted)
62    if resp[0] != 0 {
63        bail!("socks4: unexpected version byte in response: {}", resp[0]);
64    }
65    if resp[1] != 90 {
66        bail!("socks4: connection denied (status {})", resp[1]);
67    }
68
69    Ok(stream)
70}