proxy-twister 1.1.7

A flexible HTTP proxy switcher that routes traffic through different proxies (SOCKS5 or HTTP) based on target host patterns.
use std::io;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{Duration, timeout};
use tracing::{error, trace};

// SOCKS5 protocol constants
pub const SOCKS_VERSION: u8 = 0x05;
pub const NO_AUTHENTICATION: u8 = 0x00;
pub const CONNECT_COMMAND: u8 = 0x01;
pub const IPV4_TYPE: u8 = 0x01;
pub const DOMAIN_TYPE: u8 = 0x03;
pub const IPV6_TYPE: u8 = 0x04;
pub const SUCCESS_REPLY: u8 = 0x00;

pub struct Socks5Request {
    pub target: String,
    pub port: u16,
}

pub async fn forward_to_proxy(
    request: &Socks5Request,
    proxy_host: &str,
    proxy_port: u16,
) -> io::Result<TcpStream> {
    trace!("Connecting to proxy at {}:{}", proxy_host, proxy_port);
    let mut proxy = TcpStream::connect(format!("{proxy_host}:{proxy_port}")).await?;

    proxy
        .write_all(&[SOCKS_VERSION, 1, NO_AUTHENTICATION])
        .await?;
    trace!("Sent authentication request to proxy");
    let mut response = [0u8; 2];
    proxy.read_exact(&mut response).await?;
    trace!("Received authentication response: {:?}", response);

    if response[0] != SOCKS_VERSION || response[1] != NO_AUTHENTICATION {
        error!("Proxy authentication failed: {:?}", response);
        return Err(io::Error::other("Proxy authentication failed"));
    }

    proxy.write_all(&[SOCKS_VERSION]).await?;
    proxy.write_all(&[CONNECT_COMMAND]).await?;
    proxy.write_all(&[0x00]).await?;
    trace!("Sending SOCKS5 request to proxy");
    proxy.write_all(&[DOMAIN_TYPE]).await?;
    proxy.write_all(&[request.target.len() as u8]).await?;
    proxy.write_all(request.target.as_bytes()).await?;

    proxy.write_all(&request.port.to_be_bytes()).await?;
    trace!("Forwarded request to proxy");

    trace!("Waiting for proxy response with timeout");
    let mut response_header = [0u8; 4];
    match timeout(
        Duration::from_secs(10),
        proxy.read_exact(&mut response_header),
    )
    .await
    {
        Ok(Ok(_)) => trace!("Received proxy response header: {:?}", response_header),
        Ok(Err(e)) => {
            error!("Failed to read proxy response header: {}", e);
            return Err(e);
        }
        Err(_) => {
            error!("Timed out while waiting for proxy response");
            return Err(io::Error::new(
                io::ErrorKind::TimedOut,
                "Timed out while waiting for proxy response",
            ));
        }
    }

    if response_header[1] != SUCCESS_REPLY {
        error!(
            "Proxy connection failed with status: {}",
            response_header[1]
        );
        return Err(io::Error::other(format!(
            "Proxy connection failed with status: {}",
            response_header[1]
        )));
    }

    trace!("Processing proxy response address type");
    match response_header[3] {
        IPV4_TYPE => {
            let mut addr = [0u8; 6];
            match timeout(Duration::from_secs(10), proxy.read_exact(&mut addr)).await {
                Ok(Ok(_)) => trace!("Proxy bound IPv4 address: {:?}", addr),
                Ok(Err(e)) => {
                    error!("Failed to read proxy bound IPv4 address: {}", e);
                    return Err(e);
                }
                Err(_) => {
                    error!("Timed out while reading proxy bound IPv4 address");
                    return Err(io::Error::new(
                        io::ErrorKind::TimedOut,
                        "Timed out while reading proxy bound IPv4 address",
                    ));
                }
            }
        }
        IPV6_TYPE => {
            let mut addr = [0u8; 18];
            match timeout(Duration::from_secs(10), proxy.read_exact(&mut addr)).await {
                Ok(Ok(_)) => trace!("Proxy bound IPv6 address: {:?}", addr),
                Ok(Err(e)) => {
                    error!("Failed to read proxy bound IPv6 address: {}", e);
                    return Err(e);
                }
                Err(_) => {
                    error!("Timed out while reading proxy bound IPv6 address");
                    return Err(io::Error::new(
                        io::ErrorKind::TimedOut,
                        "Timed out while reading proxy bound IPv6 address",
                    ));
                }
            }
        }
        DOMAIN_TYPE => {
            let mut len = [0u8; 1];
            match timeout(Duration::from_secs(10), proxy.read_exact(&mut len)).await {
                Ok(Ok(_)) => {
                    let mut domain = vec![0u8; len[0] as usize + 2];
                    match timeout(Duration::from_secs(10), proxy.read_exact(&mut domain)).await {
                        Ok(Ok(_)) => trace!("Proxy bound domain address: {:?}", domain),
                        Ok(Err(e)) => {
                            error!("Failed to read proxy bound domain address: {}", e);
                            return Err(e);
                        }
                        Err(_) => {
                            error!("Timed out while reading proxy bound domain address");
                            return Err(io::Error::new(
                                io::ErrorKind::TimedOut,
                                "Timed out while reading proxy bound domain address",
                            ));
                        }
                    }
                }
                Ok(Err(e)) => {
                    error!("Failed to read domain length: {}", e);
                    return Err(e);
                }
                Err(_) => {
                    error!("Timed out while reading domain length");
                    return Err(io::Error::new(
                        io::ErrorKind::TimedOut,
                        "Timed out while reading domain length",
                    ));
                }
            }
        }
        _ => {
            error!(
                "Invalid address type in proxy response: {}",
                response_header[3]
            );
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Invalid address type in proxy response",
            ));
        }
    }

    Ok(proxy)
}