Skip to main content

eggress_protocol_socks/socks4/
server.rs

1use std::net::{IpAddr, Ipv4Addr, SocketAddr};
2
3use tokio::io::{AsyncReadExt, AsyncWriteExt};
4
5use super::error::Socks4Error;
6
7/// Maximum length for the SOCKS4 user ID field.
8const MAX_USER_ID_LEN: usize = 255;
9
10/// SOCKS4 reply status codes.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum Socks4Status {
14    Granted = 90,
15    Failed = 91,
16    FailedNoIdent = 92,
17    FailedDifferentUser = 93,
18}
19
20impl Socks4Status {
21    /// Convert from raw status byte.
22    pub fn from_u8(val: u8) -> Option<Self> {
23        match val {
24            90 => Some(Self::Granted),
25            91 => Some(Self::Failed),
26            92 => Some(Self::FailedNoIdent),
27            93 => Some(Self::FailedDifferentUser),
28            _ => None,
29        }
30    }
31}
32
33/// Parsed SOCKS4/4a CONNECT request.
34#[derive(Debug, Clone)]
35pub struct Socks4Request {
36    pub command: u8,
37    pub port: u16,
38    pub addr: SocketAddr,
39    pub user_id: String,
40    /// For SOCKS4a: the domain name when IP is 0.0.0.x (x != 0).
41    pub domain: Option<String>,
42}
43
44/// Read a SOCKS4/4a request from the stream.
45///
46/// Format:
47///   +----+----+----+----+----+----+----+----+----+----+....+----+
48///   | VN | CD | DSTPORT |      DSTIP        | USERID       |0x00|
49///   +----+----+----+----+----+----+----+----+----+----+....+----+
50///     1    1      2            4              variable       1
51///
52/// For SOCKS4a, when DSTIP is 0.0.0.x (x != 0), the domain follows
53/// after the NUL-terminated USERID.
54pub async fn read_socks4_request<S: tokio::io::AsyncRead + Unpin>(
55    stream: &mut S,
56) -> Result<Socks4Request, Socks4Error> {
57    let mut header = [0u8; 8];
58    stream.read_exact(&mut header).await?;
59
60    let version = header[0];
61    if version != 0x04 {
62        return Err(Socks4Error::InvalidVersion(version));
63    }
64
65    let command = header[1];
66    if command != 0x01 {
67        return Err(Socks4Error::UnsupportedCommand(command));
68    }
69
70    let port = u16::from_be_bytes([header[2], header[3]]);
71    let ip = Ipv4Addr::new(header[4], header[5], header[6], header[7]);
72
73    // Read NUL-terminated user ID (bounded at MAX_USER_ID_LEN + 1 for NUL).
74    let mut user_id_bytes = Vec::with_capacity(64);
75    let mut buf = [0u8; 1];
76    loop {
77        if user_id_bytes.len() > MAX_USER_ID_LEN {
78            return Err(Socks4Error::UserIdTooLong);
79        }
80        let n = stream.read(&mut buf).await?;
81        if n == 0 {
82            return Err(Socks4Error::MalformedRequest(
83                "unexpected EOF reading user ID".into(),
84            ));
85        }
86        if buf[0] == 0x00 {
87            break;
88        }
89        user_id_bytes.push(buf[0]);
90    }
91
92    let user_id = String::from_utf8(user_id_bytes)
93        .map_err(|_| Socks4Error::MalformedRequest("invalid UTF-8 in user ID".into()))?;
94
95    // SOCKS4a: if IP is 0.0.0.x (x != 0), read domain after user ID.
96    let domain =
97        if ip.octets()[0] == 0 && ip.octets()[1] == 0 && ip.octets()[2] == 0 && ip.octets()[3] != 0
98        {
99            let mut domain_bytes = Vec::with_capacity(256);
100            loop {
101                if domain_bytes.len() > 255 {
102                    return Err(Socks4Error::DomainTooLong);
103                }
104                let n = stream.read(&mut buf).await?;
105                if n == 0 {
106                    return Err(Socks4Error::MalformedRequest(
107                        "unexpected EOF reading domain".into(),
108                    ));
109                }
110                if buf[0] == 0x00 {
111                    break;
112                }
113                domain_bytes.push(buf[0]);
114            }
115            let domain = String::from_utf8(domain_bytes)
116                .map_err(|_| Socks4Error::MalformedRequest("invalid UTF-8 in domain".into()))?;
117            if domain.is_empty() {
118                return Err(Socks4Error::MalformedRequest(
119                    "empty domain in SOCKS4a request".into(),
120                ));
121            }
122            Some(domain)
123        } else {
124            None
125        };
126
127    let addr = SocketAddr::new(IpAddr::V4(ip), port);
128
129    Ok(Socks4Request {
130        command,
131        port,
132        addr,
133        user_id,
134        domain,
135    })
136}
137
138/// Write a SOCKS4 reply to the stream.
139///
140/// Format:
141///   +----+----+----+----+----+----+----+----+
142///   | VN | CD | DSTPORT |      DSTIP        |
143///   +----+----+----+----+----+----+----+----+
144///     1    1      2            4
145pub async fn write_socks4_reply<S: tokio::io::AsyncWrite + Unpin>(
146    stream: &mut S,
147    status: Socks4Status,
148    addr: SocketAddr,
149) -> Result<(), Socks4Error> {
150    let ip = match addr.ip() {
151        IpAddr::V4(v4) => v4.octets(),
152        IpAddr::V6(_) => Ipv4Addr::UNSPECIFIED.octets(),
153    };
154    let port = addr.port().to_be_bytes();
155    let reply: [u8; 8] = [
156        0x00,
157        status as u8,
158        port[0],
159        port[1],
160        ip[0],
161        ip[1],
162        ip[2],
163        ip[3],
164    ];
165    stream.write_all(&reply).await?;
166    stream.flush().await?;
167    Ok(())
168}