Skip to main content

eggress_protocol_socks/socks5/
server.rs

1use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
2use zeroize::Zeroizing;
3
4use crate::error::Socks5Error;
5
6/// SOCKS5 address types.
7pub const ATYP_IPV4: u8 = 0x01;
8pub const ATYP_DOMAIN: u8 = 0x03;
9pub const ATYP_IPV6: u8 = 0x04;
10
11/// SOCKS5 commands.
12pub const CMD_CONNECT: u8 = 0x01;
13pub const CMD_BIND: u8 = 0x02;
14pub const CMD_UDP_ASSOCIATE: u8 = 0x03;
15
16/// Parsed SOCKS5 command.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Socks5Command {
19    Connect,
20    Bind,
21    UdpAssociate,
22}
23
24/// Parse a SOCKS5 command byte.
25pub fn parse_command(cmd: u8) -> Result<Socks5Command, Socks5Error> {
26    match cmd {
27        CMD_CONNECT => Ok(Socks5Command::Connect),
28        CMD_BIND => Ok(Socks5Command::Bind),
29        CMD_UDP_ASSOCIATE => Ok(Socks5Command::UdpAssociate),
30        _ => Err(Socks5Error::UnsupportedCommand(cmd)),
31    }
32}
33
34/// SOCKS5 reply codes.
35pub const REP_SUCCESS: u8 = 0x00;
36pub const REP_GENERAL_FAILURE: u8 = 0x01;
37pub const REP_NOT_ALLOWED: u8 = 0x02;
38pub const REP_COMMAND_NOT_SUPPORTED: u8 = 0x07;
39pub const REP_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 0x08;
40
41/// SOCKS5 authentication methods.
42const AUTH_NONE: u8 = 0x00;
43const AUTH_USERNAME_PASSWORD: u8 = 0x02;
44const AUTH_NO_ACCEPTABLE: u8 = 0xFF;
45
46/// Username/password auth version.
47const AUTH_VERSION: u8 = 0x01;
48
49/// Maximum length for username or password in SOCKS5 auth.
50const MAX_CRED_LEN: usize = 255;
51
52/// A parsed SOCKS5 CONNECT request.
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub enum SocksAddr {
55    IPv4([u8; 4], u16),
56    Domain(String, u16),
57    IPv6([u8; 16], u16),
58}
59
60impl SocksAddr {
61    /// Returns the host as a displayable string.
62    pub fn host_str(&self) -> String {
63        match self {
64            SocksAddr::IPv4(addr, _) => format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]),
65            SocksAddr::Domain(domain, _) => domain.clone(),
66            SocksAddr::IPv6(addr, _) => {
67                // Format as [ipv6]:port
68                let segments: Vec<String> = addr
69                    .chunks(2)
70                    .map(|chunk| format!("{:02x}{:02x}", chunk[0], chunk[1]))
71                    .collect();
72                format!("[{}]", segments.join(":"))
73            }
74        }
75    }
76
77    /// Returns the port.
78    pub fn port(&self) -> u16 {
79        match self {
80            SocksAddr::IPv4(_, port) | SocksAddr::Domain(_, port) | SocksAddr::IPv6(_, port) => {
81                *port
82            }
83        }
84    }
85
86    /// Encode this address into bytes for a SOCKS5 reply.
87    pub fn encode_reply(&self) -> Result<Vec<u8>, Socks5Error> {
88        let mut buf = Vec::new();
89        match self {
90            SocksAddr::IPv4(addr, port) => {
91                buf.push(ATYP_IPV4);
92                buf.extend_from_slice(addr);
93                buf.extend_from_slice(&port.to_be_bytes());
94            }
95            SocksAddr::Domain(domain, port) => {
96                let byte_len = domain.len();
97                if byte_len > 255 {
98                    return Err(Socks5Error::DomainTooLong(byte_len));
99                }
100                buf.push(ATYP_DOMAIN);
101                buf.push(byte_len as u8);
102                buf.extend_from_slice(domain.as_bytes());
103                buf.extend_from_slice(&port.to_be_bytes());
104            }
105            SocksAddr::IPv6(addr, port) => {
106                buf.push(ATYP_IPV6);
107                buf.extend_from_slice(addr);
108                buf.extend_from_slice(&port.to_be_bytes());
109            }
110        }
111        Ok(buf)
112    }
113}
114
115/// Parse the SOCKS5 method negotiation bytes synchronously.
116///
117/// Returns `(methods, remaining)` on success, or a [`Socks5Error`] if the
118/// buffer is truncated or the version byte is wrong. Exposed for fuzzing.
119pub fn parse_method_negotiation(buf: &[u8]) -> Result<(Vec<u8>, &[u8]), Socks5Error> {
120    if buf.is_empty() {
121        return Err(Socks5Error::UnexpectedEof);
122    }
123    let version = buf[0];
124    if version != 0x05 {
125        return Err(Socks5Error::UnsupportedVersion(version));
126    }
127    if buf.len() < 2 {
128        return Err(Socks5Error::UnexpectedEof);
129    }
130    let nmethods = buf[1] as usize;
131    if buf.len() < 2 + nmethods {
132        return Err(Socks5Error::UnexpectedEof);
133    }
134    Ok((buf[2..2 + nmethods].to_vec(), &buf[2 + nmethods..]))
135}
136
137/// Parse a SOCKS5 CONNECT request synchronously.
138///
139/// Returns `(target, remaining)` on success. Exposed for fuzzing.
140pub fn parse_connect_request(buf: &[u8]) -> Result<(SocksAddr, &[u8]), Socks5Error> {
141    if buf.len() < 4 {
142        return Err(Socks5Error::UnexpectedEof);
143    }
144    let version = buf[0];
145    if version != 0x05 {
146        return Err(Socks5Error::UnsupportedVersion(version));
147    }
148    let cmd = buf[1];
149    if cmd != CMD_CONNECT {
150        return Err(Socks5Error::UnsupportedCommand(cmd));
151    }
152    let rsv = buf[2];
153    if rsv != 0 {
154        return Err(Socks5Error::InvalidReservedByte(rsv));
155    }
156    let atyp = buf[3];
157
158    let (addr, rest) = match atyp {
159        ATYP_IPV4 => {
160            if buf.len() < 4 + 4 + 2 {
161                return Err(Socks5Error::UnexpectedEof);
162            }
163            let mut octets = [0u8; 4];
164            octets.copy_from_slice(&buf[4..8]);
165            let port = u16::from_be_bytes([buf[8], buf[9]]);
166            (SocksAddr::IPv4(octets, port), &buf[10..])
167        }
168        ATYP_DOMAIN => {
169            if buf.len() < 5 {
170                return Err(Socks5Error::UnexpectedEof);
171            }
172            let len = buf[4] as usize;
173            if buf.len() < 5 + len + 2 {
174                return Err(Socks5Error::UnexpectedEof);
175            }
176            let domain_bytes = &buf[5..5 + len];
177            let domain = String::from_utf8(domain_bytes.to_vec())
178                .map_err(|e| Socks5Error::MalformedMessage(format!("invalid domain: {e}")))?;
179            let port = u16::from_be_bytes([buf[5 + len], buf[5 + len + 1]]);
180            (SocksAddr::Domain(domain, port), &buf[5 + len + 2..])
181        }
182        ATYP_IPV6 => {
183            if buf.len() < 4 + 16 + 2 {
184                return Err(Socks5Error::UnexpectedEof);
185            }
186            let mut octets = [0u8; 16];
187            octets.copy_from_slice(&buf[4..20]);
188            let port = u16::from_be_bytes([buf[20], buf[21]]);
189            (SocksAddr::IPv6(octets, port), &buf[22..])
190        }
191        _ => return Err(Socks5Error::UnsupportedAddressType(atyp)),
192    };
193
194    Ok((addr, rest))
195}
196
197/// Parse a SOCKS5 generic request synchronously (CONNECT, BIND, or UDP_ASSOCIATE).
198///
199/// Returns `(command, target, remaining)`. Exposed for fuzzing.
200pub fn parse_socks5_request(buf: &[u8]) -> Result<(Socks5Command, SocksAddr, &[u8]), Socks5Error> {
201    if buf.len() < 4 {
202        return Err(Socks5Error::UnexpectedEof);
203    }
204    let version = buf[0];
205    if version != 0x05 {
206        return Err(Socks5Error::UnsupportedVersion(version));
207    }
208    let cmd = buf[1];
209    let command = parse_command(cmd)?;
210    let rsv = buf[2];
211    if rsv != 0 {
212        return Err(Socks5Error::InvalidReservedByte(rsv));
213    }
214    let atyp = buf[3];
215
216    let (addr, rest) = match atyp {
217        ATYP_IPV4 => {
218            if buf.len() < 4 + 4 + 2 {
219                return Err(Socks5Error::UnexpectedEof);
220            }
221            let mut octets = [0u8; 4];
222            octets.copy_from_slice(&buf[4..8]);
223            let port = u16::from_be_bytes([buf[8], buf[9]]);
224            (SocksAddr::IPv4(octets, port), &buf[10..])
225        }
226        ATYP_DOMAIN => {
227            if buf.len() < 5 {
228                return Err(Socks5Error::UnexpectedEof);
229            }
230            let len = buf[4] as usize;
231            if buf.len() < 5 + len + 2 {
232                return Err(Socks5Error::UnexpectedEof);
233            }
234            let domain_bytes = &buf[5..5 + len];
235            let domain = String::from_utf8(domain_bytes.to_vec())
236                .map_err(|e| Socks5Error::MalformedMessage(format!("invalid domain: {e}")))?;
237            let port = u16::from_be_bytes([buf[5 + len], buf[5 + len + 1]]);
238            (SocksAddr::Domain(domain, port), &buf[5 + len + 2..])
239        }
240        ATYP_IPV6 => {
241            if buf.len() < 4 + 16 + 2 {
242                return Err(Socks5Error::UnexpectedEof);
243            }
244            let mut octets = [0u8; 16];
245            octets.copy_from_slice(&buf[4..20]);
246            let port = u16::from_be_bytes([buf[20], buf[21]]);
247            (SocksAddr::IPv6(octets, port), &buf[22..])
248        }
249        _ => return Err(Socks5Error::UnsupportedAddressType(atyp)),
250    };
251
252    Ok((command, addr, rest))
253}
254
255/// Read a complete method negotiation message from the client.
256///
257/// Returns the list of methods the client supports.
258pub async fn read_method_negotiation<R: AsyncRead + Unpin>(
259    reader: &mut R,
260) -> Result<Vec<u8>, Socks5Error> {
261    let version = reader.read_u8().await?;
262    if version != 0x05 {
263        return Err(Socks5Error::UnsupportedVersion(version));
264    }
265
266    let nmethods = reader.read_u8().await?;
267    let mut methods = vec![0u8; nmethods as usize];
268    reader.read_exact(&mut methods).await?;
269
270    Ok(methods)
271}
272
273/// Send a method selection response to the client.
274///
275/// If the client supports `AUTH_NONE` and no password is required, selects no auth.
276/// If `password` is Some and the client supports username/password auth, selects that.
277/// If `password` is Some but username/password auth was not offered, sends 0xFF
278/// (no acceptable methods) so unauthenticated access is never negotiated.
279/// Otherwise, sends 0xFF (no acceptable methods).
280pub async fn send_method_selection<W: AsyncWrite + Unpin>(
281    writer: &mut W,
282    methods: &[u8],
283    password: Option<&str>,
284) -> Result<(), Socks5Error> {
285    let method = if let Some(_password) = password {
286        if methods.contains(&AUTH_USERNAME_PASSWORD) {
287            AUTH_USERNAME_PASSWORD
288        } else {
289            AUTH_NO_ACCEPTABLE
290        }
291    } else if methods.contains(&AUTH_NONE) {
292        AUTH_NONE
293    } else {
294        AUTH_NO_ACCEPTABLE
295    };
296
297    writer.write_all(&[0x05, method]).await?;
298    writer.flush().await?;
299
300    if method == AUTH_NO_ACCEPTABLE {
301        return Err(Socks5Error::MethodNegotiationFailed);
302    }
303
304    Ok(())
305}
306
307/// Read and validate username/password authentication from the client.
308///
309/// Compares both username and password against the expected values using
310/// constant-time byte comparison. Returns the lossy-decoded username on
311/// success for logging/audit purposes only.
312pub async fn read_auth_request<R: AsyncRead + Unpin>(
313    reader: &mut R,
314    expected_username: &str,
315    expected_password: &str,
316) -> Result<String, Socks5Error> {
317    let version = reader.read_u8().await?;
318    if version != AUTH_VERSION {
319        return Err(Socks5Error::UnsupportedVersion(version));
320    }
321
322    let ulen = reader.read_u8().await? as usize;
323    if ulen > MAX_CRED_LEN {
324        return Err(Socks5Error::CredentialsTooLong);
325    }
326    let mut username = Zeroizing::new(vec![0u8; ulen]);
327    reader.read_exact(&mut username).await?;
328
329    let plen = reader.read_u8().await? as usize;
330    if plen > MAX_CRED_LEN {
331        return Err(Socks5Error::CredentialsTooLong);
332    }
333    let mut password_bytes = Zeroizing::new(vec![0u8; plen]);
334    reader.read_exact(&mut password_bytes).await?;
335
336    // Compare the raw wire bytes: lossy UTF-8 conversion would map distinct
337    // invalid sequences to U+FFFD, collapsing them into the same value.
338    use subtle::ConstantTimeEq;
339    let username_match: bool = username
340        .as_slice()
341        .ct_eq(expected_username.as_bytes())
342        .into();
343    let password_match: bool = password_bytes
344        .as_slice()
345        .ct_eq(expected_password.as_bytes())
346        .into();
347    if !username_match || !password_match {
348        return Err(Socks5Error::AuthFailed);
349    }
350
351    Ok(String::from_utf8_lossy(username.as_slice()).to_string())
352}
353
354/// Send an authentication response to the client.
355pub async fn send_auth_response<W: AsyncWrite + Unpin>(
356    writer: &mut W,
357    success: bool,
358) -> Result<(), Socks5Error> {
359    let status = if success { 0x00 } else { 0x01 };
360    writer.write_all(&[AUTH_VERSION, status]).await?;
361    writer.flush().await?;
362    Ok(())
363}
364
365/// Read a CONNECT request from the client.
366///
367/// Returns the target address.
368pub async fn read_connect_request<R: AsyncRead + Unpin>(
369    reader: &mut R,
370) -> Result<SocksAddr, Socks5Error> {
371    let version = reader.read_u8().await?;
372    if version != 0x05 {
373        return Err(Socks5Error::UnsupportedVersion(version));
374    }
375
376    let cmd = reader.read_u8().await?;
377    if cmd != CMD_CONNECT {
378        // The REP=0x07 acknowledgement for unsupported commands is sent by
379        // `handle_socks5_handshake`, which owns the writer half.
380        return Err(Socks5Error::UnsupportedCommand(cmd));
381    }
382
383    let rsv = reader.read_u8().await?;
384    if rsv != 0 {
385        return Err(Socks5Error::InvalidReservedByte(rsv));
386    }
387
388    let atyp = reader.read_u8().await?;
389
390    let addr = match atyp {
391        ATYP_IPV4 => {
392            let mut buf = [0u8; 4];
393            reader.read_exact(&mut buf).await?;
394            let port = reader.read_u16().await?;
395            SocksAddr::IPv4(buf, port)
396        }
397        ATYP_DOMAIN => {
398            let len = reader.read_u8().await? as usize;
399            let mut domain = vec![0u8; len];
400            reader.read_exact(&mut domain).await?;
401            let domain = String::from_utf8(domain)
402                .map_err(|e| Socks5Error::MalformedMessage(format!("invalid domain: {e}")))?;
403            let port = reader.read_u16().await?;
404            SocksAddr::Domain(domain, port)
405        }
406        ATYP_IPV6 => {
407            let mut buf = [0u8; 16];
408            reader.read_exact(&mut buf).await?;
409            let port = reader.read_u16().await?;
410            SocksAddr::IPv6(buf, port)
411        }
412        _ => return Err(Socks5Error::UnsupportedAddressType(atyp)),
413    };
414
415    Ok(addr)
416}
417
418/// Read a SOCKS5 request from the client.
419///
420/// Returns the command and target address. Does not reject non-CONNECT commands.
421pub async fn read_socks5_request<R: AsyncRead + Unpin>(
422    reader: &mut R,
423) -> Result<(Socks5Command, SocksAddr), Socks5Error> {
424    let version = reader.read_u8().await?;
425    if version != 0x05 {
426        return Err(Socks5Error::UnsupportedVersion(version));
427    }
428
429    let cmd = reader.read_u8().await?;
430    let command = parse_command(cmd)?;
431
432    let rsv = reader.read_u8().await?;
433    if rsv != 0 {
434        return Err(Socks5Error::InvalidReservedByte(rsv));
435    }
436
437    let atyp = reader.read_u8().await?;
438
439    let addr = match atyp {
440        ATYP_IPV4 => {
441            let mut buf = [0u8; 4];
442            reader.read_exact(&mut buf).await?;
443            let port = reader.read_u16().await?;
444            SocksAddr::IPv4(buf, port)
445        }
446        ATYP_DOMAIN => {
447            let len = reader.read_u8().await? as usize;
448            let mut domain = vec![0u8; len];
449            reader.read_exact(&mut domain).await?;
450            let domain = String::from_utf8(domain)
451                .map_err(|e| Socks5Error::MalformedMessage(format!("invalid domain: {e}")))?;
452            let port = reader.read_u16().await?;
453            SocksAddr::Domain(domain, port)
454        }
455        ATYP_IPV6 => {
456            let mut buf = [0u8; 16];
457            reader.read_exact(&mut buf).await?;
458            let port = reader.read_u16().await?;
459            SocksAddr::IPv6(buf, port)
460        }
461        _ => return Err(Socks5Error::UnsupportedAddressType(atyp)),
462    };
463
464    Ok((command, addr))
465}
466
467/// Send a UDP ASSOCIATE reply to the client with the relay bind address.
468pub async fn send_udp_associate_reply<W: AsyncWrite + Unpin>(
469    writer: &mut W,
470    bind_addr: &SocksAddr,
471) -> Result<(), Socks5Error> {
472    send_connect_reply(writer, REP_SUCCESS, bind_addr).await
473}
474
475/// Send a CONNECT reply to the client.
476pub async fn send_connect_reply<W: AsyncWrite + Unpin>(
477    writer: &mut W,
478    rep: u8,
479    bind_addr: &SocksAddr,
480) -> Result<(), Socks5Error> {
481    let mut reply = vec![0x05, rep, 0x00]; // version, reply, reserved
482    reply.extend_from_slice(&bind_addr.encode_reply()?);
483    writer.write_all(&reply).await?;
484    writer.flush().await?;
485    Ok(())
486}
487
488/// Handle a complete SOCKS5 server handshake.
489///
490/// This reads the method negotiation, optionally handles username/password auth,
491/// and reads the CONNECT request. Returns the target address on success.
492///
493/// # Arguments
494/// * `reader` - The stream to read from.
495/// * `writer` - The stream to write to.
496/// * `username` - If password is required, the expected username.
497/// * `password` - If Some, require username/password authentication with this password.
498pub async fn handle_socks5_handshake<R: AsyncRead + Unpin, W: AsyncWrite + Unpin>(
499    reader: &mut R,
500    writer: &mut W,
501    username: Option<&str>,
502    password: Option<&str>,
503) -> Result<SocksAddr, Socks5Error> {
504    // Step 1: Method negotiation
505    let methods = read_method_negotiation(reader).await?;
506    send_method_selection(writer, &methods, password).await?;
507
508    // Step 2: Auth (if password required)
509    if let Some(pwd) = password {
510        let user = username.unwrap_or("");
511        if let Err(e) = read_auth_request(reader, user, pwd).await {
512            // RFC 1929: always answer with a failure status before closing.
513            let _ = send_auth_response(writer, false).await;
514            return Err(e);
515        }
516        send_auth_response(writer, true).await?;
517    }
518
519    // Step 3: CONNECT request
520    match read_connect_request(reader).await {
521        Ok(target) => Ok(target),
522        Err(Socks5Error::UnsupportedCommand(cmd)) => {
523            // RFC 1928 §6: acknowledge unsupported commands with REP=0x07
524            // before closing. The target address was not readable, so the
525            // reply carries an unspecified bind address.
526            let unspecified = SocksAddr::IPv4([0, 0, 0, 0], 0);
527            let _ = send_connect_reply(writer, REP_COMMAND_NOT_SUPPORTED, &unspecified).await;
528            Err(Socks5Error::UnsupportedCommand(cmd))
529        }
530        Err(e) => Err(e),
531    }
532}
533
534/// Send a rejection reply for unsupported commands.
535pub async fn reject_command<W: AsyncWrite + Unpin>(
536    writer: &mut W,
537    target: &SocksAddr,
538) -> Result<(), Socks5Error> {
539    send_connect_reply(writer, REP_COMMAND_NOT_SUPPORTED, target).await?;
540    Ok(())
541}
542
543/// Get the success reply code.
544pub const fn success_reply() -> u8 {
545    REP_SUCCESS
546}
547
548/// Get the general failure reply code.
549pub const fn general_failure_reply() -> u8 {
550    REP_GENERAL_FAILURE
551}
552
553/// Get the command not supported reply code.
554pub const fn command_not_supported_reply() -> u8 {
555    REP_COMMAND_NOT_SUPPORTED
556}
557
558/// Get the address type not supported reply code.
559pub const fn address_type_not_supported_reply() -> u8 {
560    REP_ADDRESS_TYPE_NOT_SUPPORTED
561}
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566    use tokio::io::duplex;
567
568    #[tokio::test]
569    async fn test_method_negotiation_no_auth() {
570        let (mut client, mut server) = duplex(1024);
571
572        // Client sends: version=5, nmethods=1, method=0x00 (no auth)
573        client.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
574
575        let methods = read_method_negotiation(&mut server).await.unwrap();
576        assert_eq!(methods, vec![0x00]);
577
578        send_method_selection(&mut server, &methods, None)
579            .await
580            .unwrap();
581
582        let mut response = [0u8; 2];
583        client.read_exact(&mut response).await.unwrap();
584        assert_eq!(response, [0x05, 0x00]); // Selected no auth
585    }
586
587    #[tokio::test]
588    async fn test_method_negotiation_username_password() {
589        let (mut client, mut server) = duplex(1024);
590
591        // Client offers: no auth and username/password
592        client.write_all(&[0x05, 0x02, 0x00, 0x02]).await.unwrap();
593
594        let methods = read_method_negotiation(&mut server).await.unwrap();
595        assert_eq!(methods, vec![0x00, 0x02]);
596
597        // Server requires password
598        send_method_selection(&mut server, &methods, Some("secret"))
599            .await
600            .unwrap();
601
602        let mut response = [0u8; 2];
603        client.read_exact(&mut response).await.unwrap();
604        assert_eq!(response, [0x05, 0x02]); // Selected username/password
605    }
606
607    #[tokio::test]
608    async fn test_method_negotiation_no_acceptable() {
609        let (mut client, mut server) = duplex(1024);
610
611        // Client offers only GSSAPI (0x01) which we don't support
612        client.write_all(&[0x05, 0x01, 0x01]).await.unwrap();
613
614        let methods = read_method_negotiation(&mut server).await.unwrap();
615        let result = send_method_selection(&mut server, &methods, None).await;
616        assert!(matches!(result, Err(Socks5Error::MethodNegotiationFailed)));
617    }
618
619    #[tokio::test]
620    async fn test_password_required_rejects_auth_none_only_client() {
621        let (mut client, mut server) = duplex(1024);
622
623        // Client offers only AUTH_NONE while the server requires a password.
624        client.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
625
626        let methods = read_method_negotiation(&mut server).await.unwrap();
627        let result = send_method_selection(&mut server, &methods, Some("secret")).await;
628        assert!(matches!(result, Err(Socks5Error::MethodNegotiationFailed)));
629
630        // The client must see 0xFF, never a negotiated 0x00.
631        let mut response = [0u8; 2];
632        client.read_exact(&mut response).await.unwrap();
633        assert_eq!(response, [0x05, 0xFF]);
634    }
635
636    #[tokio::test]
637    async fn test_auth_success() {
638        let (mut client, mut server) = duplex(1024);
639
640        // Auth request: version=1, ulen=4, username="user", plen=6, password="secret"
641        client
642            .write_all(&[0x01, 0x04, b'u', b's', b'e', b'r', 0x06])
643            .await
644            .unwrap();
645        client.write_all(b"secret").await.unwrap();
646
647        let username = read_auth_request(&mut server, "user", "secret")
648            .await
649            .unwrap();
650        assert_eq!(username, "user");
651
652        send_auth_response(&mut server, true).await.unwrap();
653
654        let mut response = [0u8; 2];
655        client.read_exact(&mut response).await.unwrap();
656        assert_eq!(response, [0x01, 0x00]); // Success
657    }
658
659    #[tokio::test]
660    async fn test_auth_failure() {
661        let (mut client, mut server) = duplex(1024);
662
663        // Auth request with wrong password
664        client
665            .write_all(&[0x01, 0x04, b'u', b's', b'e', b'r', 0x05])
666            .await
667            .unwrap();
668        client.write_all(b"wrong").await.unwrap();
669
670        let result = read_auth_request(&mut server, "user", "secret").await;
671        assert!(matches!(result, Err(Socks5Error::AuthFailed)));
672    }
673
674    #[tokio::test]
675    async fn test_connect_ipv4() {
676        let (mut client, mut server) = duplex(1024);
677
678        // CONNECT request: version=5, cmd=1, rsv=0, atyp=1 (IPv4), addr=192.168.1.1, port=8080
679        client
680            .write_all(&[0x05, 0x01, 0x00, 0x01, 192, 168, 1, 1])
681            .await
682            .unwrap();
683        client.write_all(&8080u16.to_be_bytes()).await.unwrap();
684
685        let target = read_connect_request(&mut server).await.unwrap();
686        assert_eq!(target, SocksAddr::IPv4([192, 168, 1, 1], 8080));
687    }
688
689    #[tokio::test]
690    async fn test_connect_domain() {
691        let (mut client, mut server) = duplex(1024);
692
693        let domain = "example.com";
694        // version=5, cmd=1, rsv=0, atyp=3, len, domain, port
695        client
696            .write_all(&[0x05, 0x01, 0x00, 0x03, domain.len() as u8])
697            .await
698            .unwrap();
699        client.write_all(domain.as_bytes()).await.unwrap();
700        client.write_all(&443u16.to_be_bytes()).await.unwrap();
701
702        let target = read_connect_request(&mut server).await.unwrap();
703        assert_eq!(target, SocksAddr::Domain("example.com".to_string(), 443));
704    }
705
706    #[tokio::test]
707    async fn test_connect_ipv6() {
708        let (mut client, mut server) = duplex(1024);
709
710        // CONNECT request: version=5, cmd=1, rsv=0, atyp=4 (IPv6), addr=::1, port=443
711        let ipv6_addr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
712        client.write_all(&[0x05, 0x01, 0x00, 0x04]).await.unwrap();
713        client.write_all(&ipv6_addr).await.unwrap();
714        client.write_all(&443u16.to_be_bytes()).await.unwrap();
715
716        let target = read_connect_request(&mut server).await.unwrap();
717        assert_eq!(target, SocksAddr::IPv6(ipv6_addr, 443));
718    }
719
720    #[tokio::test]
721    async fn test_connect_reply_success() {
722        let (mut client, mut server) = duplex(1024);
723
724        let bind_addr = SocksAddr::IPv4([0, 0, 0, 0], 0);
725        send_connect_reply(&mut server, REP_SUCCESS, &bind_addr)
726            .await
727            .unwrap();
728
729        let mut response = [0u8; 10];
730        client.read_exact(&mut response).await.unwrap();
731        assert_eq!(response[0], 0x05); // version
732        assert_eq!(response[1], 0x00); // success
733        assert_eq!(response[2], 0x00); // reserved
734        assert_eq!(response[3], 0x01); // atyp IPv4
735    }
736
737    #[tokio::test]
738    async fn test_unsupported_version() {
739        let (mut client, mut server) = duplex(1024);
740
741        // Send version 4 instead of 5
742        client.write_all(&[0x04, 0x01, 0x00]).await.unwrap();
743
744        let result = read_method_negotiation(&mut server).await;
745        assert!(matches!(result, Err(Socks5Error::UnsupportedVersion(0x04))));
746    }
747
748    #[tokio::test]
749    async fn test_unsupported_command() {
750        let (mut client, mut server) = duplex(1024);
751
752        // BIND command (0x02)
753        let ipv4_addr = [192, 168, 1, 1];
754        client.write_all(&[0x05, 0x02, 0x00, 0x01]).await.unwrap();
755        client.write_all(&ipv4_addr).await.unwrap();
756        client.write_all(&80u16.to_be_bytes()).await.unwrap();
757
758        let result = read_connect_request(&mut server).await;
759        assert!(matches!(result, Err(Socks5Error::UnsupportedCommand(0x02))));
760    }
761
762    #[tokio::test]
763    async fn test_unsupported_address_type() {
764        let (mut client, mut server) = duplex(1024);
765
766        // atyp=0x05 (unsupported)
767        client.write_all(&[0x05, 0x01, 0x00, 0x05]).await.unwrap();
768
769        let result = read_connect_request(&mut server).await;
770        assert!(matches!(
771            result,
772            Err(Socks5Error::UnsupportedAddressType(0x05))
773        ));
774    }
775
776    #[tokio::test]
777    async fn test_reject_bind_command() {
778        let (mut client, mut server) = duplex(1024);
779
780        let target = SocksAddr::IPv4([192, 168, 1, 1], 80);
781        reject_command(&mut server, &target).await.unwrap();
782
783        let mut response = [0u8; 10];
784        client.read_exact(&mut response).await.unwrap();
785        assert_eq!(response[0], 0x05); // version
786        assert_eq!(response[1], 0x07); // command not supported (RFC 1928 §6)
787    }
788
789    #[tokio::test]
790    async fn test_reject_unknown_command() {
791        let (mut client, mut server) = duplex(1024);
792
793        let target = SocksAddr::IPv4([192, 168, 1, 1], 80);
794        reject_command(&mut server, &target).await.unwrap();
795
796        let mut response = [0u8; 10];
797        client.read_exact(&mut response).await.unwrap();
798        assert_eq!(response[0], 0x05); // version
799        assert_eq!(response[1], 0x07); // command not supported
800    }
801
802    #[tokio::test]
803    async fn test_creds_too_long() {
804        // ulen is u8 so max 255; CredentialsTooLong can only happen
805        // if we manually construct bad data. Verify boundary with 255-byte credential.
806    }
807
808    #[tokio::test]
809    async fn test_boundary_credentials_length() {
810        let (mut client, mut server) = duplex(2048);
811
812        let username = "a".repeat(255);
813        let password = "b".repeat(255);
814
815        // Auth request: version=1, ulen=255, username, plen=255, password
816        client.write_all(&[0x01, 255]).await.unwrap();
817        client.write_all(username.as_bytes()).await.unwrap();
818        client.write_all(&[255]).await.unwrap();
819        client.write_all(password.as_bytes()).await.unwrap();
820
821        let result = read_auth_request(&mut server, &username, &password).await;
822        assert!(result.is_ok());
823        assert_eq!(result.unwrap(), username);
824    }
825
826    #[tokio::test]
827    async fn test_full_handshake_no_auth() {
828        let (mut client, mut server) = duplex(1024);
829
830        // Method negotiation: client offers no auth
831        client.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
832
833        // Server reads and selects method
834        let methods = read_method_negotiation(&mut server).await.unwrap();
835        send_method_selection(&mut server, &methods, None)
836            .await
837            .unwrap();
838
839        // Read method selection response
840        let mut response = [0u8; 2];
841        client.read_exact(&mut response).await.unwrap();
842        assert_eq!(response, [0x05, 0x00]);
843
844        // CONNECT request
845        client
846            .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
847            .await
848            .unwrap();
849        client.write_all(&443u16.to_be_bytes()).await.unwrap();
850
851        let target = read_connect_request(&mut server).await.unwrap();
852        assert_eq!(target, SocksAddr::IPv4([10, 0, 0, 1], 443));
853
854        // Send success reply
855        let bind_addr = SocksAddr::IPv4([0, 0, 0, 0], 0);
856        send_connect_reply(&mut server, REP_SUCCESS, &bind_addr)
857            .await
858            .unwrap();
859
860        let mut reply = [0u8; 10];
861        client.read_exact(&mut reply).await.unwrap();
862        assert_eq!(reply[0], 0x05);
863        assert_eq!(reply[1], 0x00);
864    }
865
866    #[tokio::test]
867    async fn test_full_handshake_with_auth() {
868        let (mut client, mut server) = duplex(2048);
869
870        // Method negotiation: client offers both methods
871        client.write_all(&[0x05, 0x02, 0x00, 0x02]).await.unwrap();
872
873        // Server requires password
874        let methods = read_method_negotiation(&mut server).await.unwrap();
875        send_method_selection(&mut server, &methods, Some("mypass"))
876            .await
877            .unwrap();
878
879        // Read method selection
880        let mut response = [0u8; 2];
881        client.read_exact(&mut response).await.unwrap();
882        assert_eq!(response, [0x05, 0x02]);
883
884        // Auth request
885        client
886            .write_all(&[0x01, 0x04, b'u', b's', b'e', b'r'])
887            .await
888            .unwrap();
889        client
890            .write_all(&[0x06, b'm', b'y', b'p', b'a', b's', b's'])
891            .await
892            .unwrap();
893
894        let username = read_auth_request(&mut server, "user", "mypass")
895            .await
896            .unwrap();
897        assert_eq!(username, "user");
898        send_auth_response(&mut server, true).await.unwrap();
899
900        // Read auth response
901        let mut auth_response = [0u8; 2];
902        client.read_exact(&mut auth_response).await.unwrap();
903        assert_eq!(auth_response, [0x01, 0x00]);
904
905        // CONNECT request
906        let domain = "example.com";
907        client
908            .write_all(&[0x05, 0x01, 0x00, 0x03, domain.len() as u8])
909            .await
910            .unwrap();
911        client.write_all(domain.as_bytes()).await.unwrap();
912        client.write_all(&443u16.to_be_bytes()).await.unwrap();
913
914        let target = read_connect_request(&mut server).await.unwrap();
915        assert_eq!(target, SocksAddr::Domain("example.com".to_string(), 443));
916    }
917
918    #[tokio::test]
919    async fn test_fragged_handshake() {
920        let (mut client, mut server) = duplex(1024);
921
922        // Send method negotiation in fragments
923        client.write_all(&[0x05]).await.unwrap();
924        // Small delay to simulate fragmentation
925        tokio::time::sleep(std::time::Duration::from_millis(10)).await;
926        client.write_all(&[0x01]).await.unwrap();
927        tokio::time::sleep(std::time::Duration::from_millis(10)).await;
928        client.write_all(&[0x00]).await.unwrap();
929
930        let methods = read_method_negotiation(&mut server).await.unwrap();
931        assert_eq!(methods, vec![0x00]);
932    }
933
934    #[tokio::test]
935    async fn test_socks_addr_display() {
936        let ipv4 = SocksAddr::IPv4([192, 168, 1, 1], 8080);
937        assert_eq!(ipv4.host_str(), "192.168.1.1");
938        assert_eq!(ipv4.port(), 8080);
939
940        let domain = SocksAddr::Domain("example.com".to_string(), 443);
941        assert_eq!(domain.host_str(), "example.com");
942        assert_eq!(domain.port(), 443);
943
944        let ipv6 = SocksAddr::IPv6([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 443);
945        assert_eq!(ipv6.port(), 443);
946    }
947
948    #[tokio::test]
949    async fn test_socks_addr_encode_reply() {
950        let ipv4 = SocksAddr::IPv4([192, 168, 1, 1], 8080);
951        let encoded = ipv4.encode_reply().unwrap();
952        assert_eq!(encoded[0], ATYP_IPV4);
953        assert_eq!(&encoded[1..5], &[192, 168, 1, 1]);
954        assert_eq!(&encoded[5..7], &8080u16.to_be_bytes());
955
956        let domain = SocksAddr::Domain("example.com".to_string(), 443);
957        let encoded = domain.encode_reply().unwrap();
958        assert_eq!(encoded[0], ATYP_DOMAIN);
959        assert_eq!(encoded[1], 11); // "example.com" length
960        assert_eq!(&encoded[2..13], b"example.com");
961        assert_eq!(&encoded[13..15], &443u16.to_be_bytes());
962    }
963
964    #[test]
965    fn test_parse_command() {
966        assert_eq!(parse_command(0x01).unwrap(), Socks5Command::Connect);
967        assert_eq!(parse_command(0x02).unwrap(), Socks5Command::Bind);
968        assert_eq!(parse_command(0x03).unwrap(), Socks5Command::UdpAssociate);
969        assert!(parse_command(0x04).is_err());
970        assert!(parse_command(0xFF).is_err());
971    }
972
973    #[tokio::test]
974    async fn test_udp_associate_ipv4() {
975        let (mut client, mut server) = duplex(1024);
976
977        // UDP ASSOCIATE request: version=5, cmd=3, rsv=0, atyp=1 (IPv4), addr=0.0.0.0, port=0
978        client
979            .write_all(&[0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0])
980            .await
981            .unwrap();
982        client.write_all(&0u16.to_be_bytes()).await.unwrap();
983
984        let (cmd, target) = read_socks5_request(&mut server).await.unwrap();
985        assert_eq!(cmd, Socks5Command::UdpAssociate);
986        assert_eq!(target, SocksAddr::IPv4([0, 0, 0, 0], 0));
987    }
988
989    #[tokio::test]
990    async fn test_udp_associate_ipv6() {
991        let (mut client, mut server) = duplex(1024);
992
993        let ipv6_addr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
994        client.write_all(&[0x05, 0x03, 0x00, 0x04]).await.unwrap();
995        client.write_all(&ipv6_addr).await.unwrap();
996        client.write_all(&0u16.to_be_bytes()).await.unwrap();
997
998        let (cmd, target) = read_socks5_request(&mut server).await.unwrap();
999        assert_eq!(cmd, Socks5Command::UdpAssociate);
1000        assert_eq!(target, SocksAddr::IPv6(ipv6_addr, 0));
1001    }
1002
1003    #[tokio::test]
1004    async fn test_udp_associate_domain() {
1005        let (mut client, mut server) = duplex(1024);
1006
1007        let domain = "example.com";
1008        client
1009            .write_all(&[0x05, 0x03, 0x00, 0x03, domain.len() as u8])
1010            .await
1011            .unwrap();
1012        client.write_all(domain.as_bytes()).await.unwrap();
1013        client.write_all(&0u16.to_be_bytes()).await.unwrap();
1014
1015        let (cmd, target) = read_socks5_request(&mut server).await.unwrap();
1016        assert_eq!(cmd, Socks5Command::UdpAssociate);
1017        assert_eq!(target, SocksAddr::Domain("example.com".to_string(), 0));
1018    }
1019
1020    #[tokio::test]
1021    async fn test_connect_still_works_via_read_socks5_request() {
1022        let (mut client, mut server) = duplex(1024);
1023
1024        client
1025            .write_all(&[0x05, 0x01, 0x00, 0x01, 192, 168, 1, 1])
1026            .await
1027            .unwrap();
1028        client.write_all(&8080u16.to_be_bytes()).await.unwrap();
1029
1030        let (cmd, target) = read_socks5_request(&mut server).await.unwrap();
1031        assert_eq!(cmd, Socks5Command::Connect);
1032        assert_eq!(target, SocksAddr::IPv4([192, 168, 1, 1], 8080));
1033    }
1034
1035    #[tokio::test]
1036    async fn test_reject_bind_only_not_udp_associate() {
1037        let (mut client, mut server) = duplex(1024);
1038
1039        let target = SocksAddr::IPv4([192, 168, 1, 1], 80);
1040        reject_command(&mut server, &target).await.unwrap();
1041
1042        let mut response = [0u8; 10];
1043        client.read_exact(&mut response).await.unwrap();
1044        assert_eq!(response[0], 0x05);
1045        assert_eq!(response[1], 0x07); // command not supported (RFC 1928 §6)
1046    }
1047
1048    #[tokio::test]
1049    async fn test_send_udp_associate_reply() {
1050        let (mut client, mut server) = duplex(1024);
1051
1052        let bind_addr = SocksAddr::IPv4([127, 0, 0, 1], 1080);
1053        send_udp_associate_reply(&mut server, &bind_addr)
1054            .await
1055            .unwrap();
1056
1057        let mut response = [0u8; 10];
1058        client.read_exact(&mut response).await.unwrap();
1059        assert_eq!(response[0], 0x05);
1060        assert_eq!(response[1], 0x00); // success
1061        assert_eq!(response[3], 0x01); // atyp IPv4
1062        assert_eq!(&response[4..8], &[127, 0, 0, 1]);
1063        assert_eq!(&response[8..10], &1080u16.to_be_bytes());
1064    }
1065
1066    #[test]
1067    fn parse_socks5_request_rsv_zero_accepted() {
1068        let buf = [0x05, 0x01, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50];
1069        let result = parse_socks5_request(&buf);
1070        assert!(result.is_ok());
1071    }
1072
1073    #[test]
1074    fn parse_socks5_request_rsv_nonzero_rejected() {
1075        let buf = [0x05, 0x01, 0x01, 0x01, 127, 0, 0, 1, 0x00, 0x50];
1076        let result = parse_socks5_request(&buf);
1077        assert!(matches!(
1078            result,
1079            Err(Socks5Error::InvalidReservedByte(0x01))
1080        ));
1081    }
1082
1083    #[test]
1084    fn parse_connect_request_rsv_nonzero_rejected() {
1085        let buf = [0x05, 0x01, 0x42, 0x01, 127, 0, 0, 1, 0x00, 0x50];
1086        let result = parse_connect_request(&buf);
1087        assert!(matches!(
1088            result,
1089            Err(Socks5Error::InvalidReservedByte(0x42))
1090        ));
1091    }
1092
1093    #[tokio::test]
1094    async fn read_socks5_request_rsv_nonzero_rejected() {
1095        let (mut client, mut server) = duplex(1024);
1096
1097        // version=5, cmd=1 (CONNECT), rsv=0x01 (non-zero), atyp=1 (IPv4)
1098        client
1099            .write_all(&[0x05, 0x01, 0x01, 0x01, 127, 0, 0, 1])
1100            .await
1101            .unwrap();
1102        client.write_all(&80u16.to_be_bytes()).await.unwrap();
1103
1104        let result = read_socks5_request(&mut server).await;
1105        assert!(matches!(
1106            result,
1107            Err(Socks5Error::InvalidReservedByte(0x01))
1108        ));
1109    }
1110
1111    #[tokio::test]
1112    async fn read_connect_request_rsv_nonzero_rejected() {
1113        let (mut client, mut server) = duplex(1024);
1114
1115        // version=5, cmd=1 (CONNECT), rsv=0xFF, atyp=1 (IPv4)
1116        client
1117            .write_all(&[0x05, 0x01, 0xFF, 0x01, 127, 0, 0, 1])
1118            .await
1119            .unwrap();
1120        client.write_all(&80u16.to_be_bytes()).await.unwrap();
1121
1122        let result = read_connect_request(&mut server).await;
1123        assert!(matches!(
1124            result,
1125            Err(Socks5Error::InvalidReservedByte(0xFF))
1126        ));
1127    }
1128
1129    #[test]
1130    fn encode_reply_domain_255_bytes_ok() {
1131        let domain = "a".repeat(255);
1132        let addr = SocksAddr::Domain(domain, 80);
1133        let encoded = addr.encode_reply().unwrap();
1134        assert_eq!(encoded[0], ATYP_DOMAIN);
1135        assert_eq!(encoded[1], 255);
1136        assert_eq!(&encoded[2..257], "a".repeat(255).as_bytes());
1137        assert_eq!(&encoded[257..259], &80u16.to_be_bytes());
1138    }
1139
1140    #[test]
1141    fn encode_reply_domain_256_bytes_error() {
1142        let domain = "a".repeat(256);
1143        let addr = SocksAddr::Domain(domain, 80);
1144        let result = addr.encode_reply();
1145        assert!(matches!(result, Err(Socks5Error::DomainTooLong(256))));
1146    }
1147
1148    #[test]
1149    fn encode_reply_multibyte_utf8_domain_exceeding_255_bytes() {
1150        // Each 'é' is 2 bytes in UTF-8. 128 * 2 = 256 bytes > 255
1151        let domain = "é".repeat(128);
1152        let addr = SocksAddr::Domain(domain, 80);
1153        let result = addr.encode_reply();
1154        assert!(matches!(result, Err(Socks5Error::DomainTooLong(256))));
1155    }
1156
1157    #[test]
1158    fn encode_reply_ipv4_unchanged() {
1159        let addr = SocksAddr::IPv4([192, 168, 1, 1], 8080);
1160        let encoded = addr.encode_reply().unwrap();
1161        assert_eq!(encoded, vec![ATYP_IPV4, 192, 168, 1, 1, 0x1F, 0x90]);
1162    }
1163
1164    #[test]
1165    fn encode_reply_ipv6_unchanged() {
1166        let addr = SocksAddr::IPv6([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 443);
1167        let encoded = addr.encode_reply().unwrap();
1168        assert_eq!(encoded[0], ATYP_IPV6);
1169        assert_eq!(
1170            &encoded[1..17],
1171            &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
1172        );
1173        assert_eq!(&encoded[17..19], &443u16.to_be_bytes());
1174    }
1175
1176    #[test]
1177    fn encode_reply_domain_unchanged_for_valid() {
1178        let addr = SocksAddr::Domain("example.com".to_string(), 443);
1179        let encoded = addr.encode_reply().unwrap();
1180        assert_eq!(encoded[0], ATYP_DOMAIN);
1181        assert_eq!(encoded[1], 11);
1182        assert_eq!(&encoded[2..13], b"example.com");
1183        assert_eq!(&encoded[13..15], &443u16.to_be_bytes());
1184    }
1185
1186    #[test]
1187    fn udp_rsv_first_byte_nonzero_rejected() {
1188        use super::super::udp_codec::{decode_socks5_udp_datagram, UdpCodecError};
1189        let pkt = vec![0x01, 0x00, 0x00, ATYP_IPV4, 1, 2, 3, 4, 0x00, 0x50];
1190        let result = decode_socks5_udp_datagram(&pkt);
1191        assert!(matches!(result, Err(UdpCodecError::BadReserved)));
1192    }
1193
1194    #[test]
1195    fn udp_rsv_second_byte_nonzero_rejected() {
1196        use super::super::udp_codec::{decode_socks5_udp_datagram, UdpCodecError};
1197        let pkt = vec![0x00, 0x01, 0x00, ATYP_IPV4, 1, 2, 3, 4, 0x00, 0x50];
1198        let result = decode_socks5_udp_datagram(&pkt);
1199        assert!(matches!(result, Err(UdpCodecError::BadReserved)));
1200    }
1201}