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
38pub 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}