zeloxy 0.5.1

A library for creating lightweight, async, and lag-free proxy connections.
Documentation
use bytes::{BufMut, BytesMut};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;

use crate::proxy::ProxyAuth;
use crate::{ErrorKind, ProxyError, ProxyResult};

const PROXY_VERSION: u8 = 0x04;
const CONNECT_COMMAND: u8 = 0x01;

/// Статус ответа SOCKS4 прокси
#[derive(Debug, PartialEq, Eq)]
enum ResponseStatus {
  Success,
  RequestRejected,
  IdentdError,
  AuthError,
  UnknownResponse,
}

impl ResponseStatus {
  pub fn from(id: u8) -> Self {
    match id {
      0x5a => Self::Success,
      0x5b => Self::RequestRejected,
      0x5c => Self::IdentdError,
      0x5d => Self::AuthError,
      _ => Self::UnknownResponse,
    }
  }
}

/// Функция создания подключения с SOCKS4 прокси
pub async fn connect_socks4(stream: &mut TcpStream, target_host: String, target_port: u16, auth: &Option<ProxyAuth>) -> ProxyResult<()> {
  let mut req = BytesMut::with_capacity(512);
  req.put_u8(PROXY_VERSION);
  req.put_u8(CONNECT_COMMAND);
  req.put_u16(target_port);

  let ipv4_addr = target_host.parse::<std::net::Ipv4Addr>();

  if let Ok(ipv4) = ipv4_addr {
    req.put_slice(&ipv4.octets());
  } else {
    req.put_slice(&[0x00, 0x00, 0x00, 0x01]);
  }

  if let Some(a) = auth {
    let user_id = match a {
      ProxyAuth::Ident { user_id } => user_id,
      _ => {
        return Err(ProxyError::new(
          ErrorKind::AuthFailed,
          "specified incorrect authorization method for SOCKS4",
        ));
      }
    };

    req.put_slice(user_id.as_bytes());
  } else {
    req.put_u8(0x00);
  }

  if ipv4_addr.is_err() {
    if target_host.len() > 255 {
      return Err(ProxyError::new(ErrorKind::InvalidData, "target host is too long"));
    }

    req.put_slice(target_host.as_bytes());
    req.put_u8(0x00);
  }

  stream.write_all(&req).await?;

  let mut resp = [0u8; 8];
  stream.read_exact(&mut resp).await?;

  // В SOCKS4 здесь обычно всегда 0x00
  if resp[0] != 0x00 {
    return Err(ProxyError::new(ErrorKind::InvalidData, "invalid proxy response"));
  }

  let response_status = ResponseStatus::from(resp[1]);

  if response_status != ResponseStatus::Success {
    return Err(ProxyError::new(
      ErrorKind::NotConnected,
      format!("unsuccessful proxy response status: {:?}", response_status),
    ));
  }

  Ok(())
}