//! SOCKS4/5 Client Implementation use crate::error::{ProxyError, Result}; use std::net::{IpAddr, SocketAddr}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; #[derive(Debug, Clone, Copy)] pub struct SocksBoundAddr { pub addr: SocketAddr, } pub async fn connect_socks4( stream: &mut TcpStream, target: SocketAddr, user_id: Option<&str>, ) -> Result { let ip = match target.ip() { IpAddr::V4(ip) => ip, IpAddr::V6(_) => { return Err(ProxyError::Proxy( "SOCKS4 does not support IPv6".to_string(), )); } }; let port = target.port(); let user = user_id.unwrap_or("").as_bytes(); // VN (4) | CD (1) | DSTPORT (2) | DSTIP (4) | USERID (variable) | NULL (1) let mut buf = Vec::with_capacity(9 + user.len()); buf.push(4); // VN buf.push(1); // CD (CONNECT) buf.extend_from_slice(&port.to_be_bytes()); buf.extend_from_slice(&ip.octets()); buf.extend_from_slice(user); buf.push(0); // NULL stream.write_all(&buf).await.map_err(ProxyError::Io)?; // Response: VN (1) | CD (1) | DSTPORT (2) | DSTIP (4) let mut resp = [0u8; 8]; stream.read_exact(&mut resp).await.map_err(ProxyError::Io)?; if resp[1] != 90 { return Err(ProxyError::Proxy(format!( "SOCKS4 request rejected: code {}", resp[1] ))); } let bound_port = u16::from_be_bytes([resp[2], resp[3]]); let bound_ip = IpAddr::from([resp[4], resp[5], resp[6], resp[7]]); Ok(SocksBoundAddr { addr: SocketAddr::new(bound_ip, bound_port), }) } pub async fn connect_socks5( stream: &mut TcpStream, target: SocketAddr, username: Option<&str>, password: Option<&str>, ) -> Result { // 1. Auth negotiation // VER (1) | NMETHODS (1) | METHODS (variable) let mut methods = vec![0u8]; // No auth if username.is_some() { methods.push(2u8); // Username/Password } let mut buf = vec![5u8, methods.len() as u8]; buf.extend_from_slice(&methods); stream.write_all(&buf).await.map_err(ProxyError::Io)?; let mut resp = [0u8; 2]; stream.read_exact(&mut resp).await.map_err(ProxyError::Io)?; if resp[0] != 5 { return Err(ProxyError::Proxy("Invalid SOCKS5 version".to_string())); } match resp[1] { 0 => {} // No auth 2 => { // Username/Password auth if let (Some(u), Some(p)) = (username, password) { let u_bytes = u.as_bytes(); let p_bytes = p.as_bytes(); let mut auth_buf = Vec::with_capacity(3 + u_bytes.len() + p_bytes.len()); auth_buf.push(1); // VER auth_buf.push(u_bytes.len() as u8); auth_buf.extend_from_slice(u_bytes); auth_buf.push(p_bytes.len() as u8); auth_buf.extend_from_slice(p_bytes); stream.write_all(&auth_buf).await.map_err(ProxyError::Io)?; let mut auth_resp = [0u8; 2]; stream .read_exact(&mut auth_resp) .await .map_err(ProxyError::Io)?; if auth_resp[1] != 0 { return Err(ProxyError::Proxy( "SOCKS5 authentication failed".to_string(), )); } } else { return Err(ProxyError::Proxy( "SOCKS5 server requires authentication".to_string(), )); } } _ => { return Err(ProxyError::Proxy( "Unsupported SOCKS5 auth method".to_string(), )); } } // 2. Connection request // VER (1) | CMD (1) | RSV (1) | ATYP (1) | DST.ADDR (variable) | DST.PORT (2) let mut req = vec![5u8, 1u8, 0u8]; // CONNECT match target { SocketAddr::V4(v4) => { req.push(1u8); // IPv4 req.extend_from_slice(&v4.ip().octets()); } SocketAddr::V6(v6) => { req.push(4u8); // IPv6 req.extend_from_slice(&v6.ip().octets()); } } req.extend_from_slice(&target.port().to_be_bytes()); stream.write_all(&req).await.map_err(ProxyError::Io)?; // Response let mut head = [0u8; 4]; stream.read_exact(&mut head).await.map_err(ProxyError::Io)?; if head[1] != 0 { return Err(ProxyError::Proxy(format!( "SOCKS5 request failed: code {}", head[1] ))); } // Parse bound address from response. let bound_addr = match head[3] { 1 => { // IPv4 let mut addr = [0u8; 4 + 2]; stream.read_exact(&mut addr).await.map_err(ProxyError::Io)?; let ip = IpAddr::from([addr[0], addr[1], addr[2], addr[3]]); let port = u16::from_be_bytes([addr[4], addr[5]]); SocketAddr::new(ip, port) } 3 => { // Domain let mut len = [0u8; 1]; stream.read_exact(&mut len).await.map_err(ProxyError::Io)?; let mut addr = vec![0u8; len[0] as usize + 2]; stream.read_exact(&mut addr).await.map_err(ProxyError::Io)?; // Domain-bound response is not useful for KDF IP material. let port_pos = addr.len().saturating_sub(2); let port = u16::from_be_bytes([addr[port_pos], addr[port_pos + 1]]); SocketAddr::new(IpAddr::from([0, 0, 0, 0]), port) } 4 => { // IPv6 let mut addr = [0u8; 16 + 2]; stream.read_exact(&mut addr).await.map_err(ProxyError::Io)?; let ip = IpAddr::from(<[u8; 16]>::try_from(&addr[..16]).map_err(|_| { ProxyError::Proxy("Invalid SOCKS5 IPv6 bound address".to_string()) })?); let port = u16::from_be_bytes([addr[16], addr[17]]); SocketAddr::new(ip, port) } _ => { return Err(ProxyError::Proxy( "Invalid address type in SOCKS5 response".to_string(), )); } }; Ok(SocksBoundAddr { addr: bound_addr }) }