Skip to main content

proxifier_rs/
socks4.rs

1#![doc = include_str!("../SOCKS4.md")]
2use tokio::{
3    io::{AsyncReadExt, AsyncWriteExt},
4    net::TcpStream,
5};
6
7use crate::{Context, errors};
8static VERSION_4: u8 = 0x04;
9static CONNECT_REQUEST: u8 = 0x01;
10static NULL: u8 = 0x00;
11
12#[derive(Debug)]
13pub(crate) struct Reply;
14
15impl TryFrom<u8> for Reply {
16    type Error = errors::Error;
17
18    fn try_from(value: u8) -> Result<Self, Self::Error> {
19        match value {
20            90 => Ok(Reply),
21            91 => Err(errors::Error::ProxyResponseNotOk(
22                "Request rejected or failed.".into(),
23            )),
24            92 => Err(errors::Error::ProxyResponseNotOk(
25                "Request rejected due to inability to connect to identd on the client.".into(),
26            )),
27            93 => Err(errors::Error::ProxyResponseNotOk(
28                "Request rejected because the client program and identd report different user-IDs."
29                    .into(),
30            )),
31            _ => Err(errors::Error::ProxyResponseNotOk(
32                "unknown status reply from proxy server".into(),
33            )),
34        }
35    }
36}
37
38/// Connects to the SOCKS4 proxy server and returns a [TcpStream] which can be used to relay network packets from the proxy server towards destination target
39pub async fn connect(ctx: Context) -> crate::Result<TcpStream> {
40    let proxy = ctx.proxy;
41    let target = ctx.destination;
42
43    let mut conn = TcpStream::connect(proxy).await?;
44    let mut packet = [0u8; 9];
45
46    packet[0] = VERSION_4;
47    packet[1] = CONNECT_REQUEST;
48
49    packet[2..4].copy_from_slice(&target.port().to_be_bytes());
50    packet[4..8].copy_from_slice(&target.ip().octets());
51    packet[8] = NULL;
52
53    conn.write_all(&packet).await?;
54
55    let mut reply = [0u8; 8];
56    let n = conn.read(&mut reply[..]).await?;
57    if n == 0 {
58        return Err(errors::Error::ProxyResponseNotOk(
59            "proxy server didn't reply".into(),
60        ));
61    }
62
63    Reply::try_from(reply[1])?;
64    Ok(conn)
65}