Skip to content

feat(client): add proxy::SocksV4 and proxy::SocksV5 connectors #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/client/legacy/connect/proxy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Proxy helpers

mod socks;
mod tunnel;

pub use self::socks::{SocksV4, SocksV5};
pub use self::tunnel::Tunnel;
109 changes: 109 additions & 0 deletions src/client/legacy/connect/proxy/socks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
mod v5;
pub use v5::{SocksV5, SocksV5Error};

mod v4;
pub use v4::{SocksV4, SocksV4Error};

use hyper::rt::Read;

#[derive(Debug)]
pub enum SocksError<C> {
Inner(C),
Io(std::io::Error),

DnsFailure,
MissingHost,
MissingPort,

V4(SocksV4Error),
V5(SocksV5Error),

Parsing(ParsingError),
Serialize(SerializeError),
}

#[derive(Debug)]
pub enum ParsingError {
Incomplete,
Other,
}

#[derive(Debug)]
pub enum SerializeError {
WouldOverflow,
}

async fn read_message<T, M, C>(mut conn: &mut T, buf: &mut [u8]) -> Result<M, SocksError<C>>
where
T: Read + Unpin,
M: for<'a> TryFrom<&'a [u8], Error = ParsingError>,
{
let mut n = 0;
loop {
let read = crate::rt::read(&mut conn, buf).await?;

if read == 0 {
return Err(
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof").into(),
);
}

n += read;
match M::try_from(&buf[..n]) {
Err(ParsingError::Incomplete) => continue,
Err(err) => return Err(err.into()),
Ok(res) => return Ok(res),
}
}
}

impl<C> std::fmt::Display for SocksError<C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("SOCKS error: ")?;

match self {
Self::Inner(_) => f.write_str("failed to create underlying connection"),
Self::Io(_) => f.write_str("io error during SOCKS handshake"),

Self::DnsFailure => f.write_str("could not resolve to acceptable address type"),
Self::MissingHost => f.write_str("missing destination host"),
Self::MissingPort => f.write_str("missing destination port"),

Self::Parsing(_) => f.write_str("failed parsing server response"),
Self::Serialize(_) => f.write_str("failed serialize request"),

Self::V4(e) => e.fmt(f),
Self::V5(e) => e.fmt(f),
}
}
}

impl<C> From<std::io::Error> for SocksError<C> {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}

impl<C> From<ParsingError> for SocksError<C> {
fn from(err: ParsingError) -> Self {
Self::Parsing(err)
}
}

impl<C> From<SerializeError> for SocksError<C> {
fn from(err: SerializeError) -> Self {
Self::Serialize(err)
}
}

impl<C> From<SocksV4Error> for SocksError<C> {
fn from(err: SocksV4Error) -> Self {
Self::V4(err)
}
}

impl<C> From<SocksV5Error> for SocksError<C> {
fn from(err: SocksV5Error) -> Self {
Self::V5(err)
}
}
22 changes: 22 additions & 0 deletions src/client/legacy/connect/proxy/socks/v4/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use super::Status;

#[derive(Debug)]
pub enum SocksV4Error {
IpV6,
Command(Status),
}

impl From<Status> for SocksV4Error {
fn from(err: Status) -> Self {
Self::Command(err)
}
}

impl std::fmt::Display for SocksV4Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IpV6 => f.write_str("IPV6 is not supported"),
Self::Command(status) => status.fmt(f),
}
}
}
136 changes: 136 additions & 0 deletions src/client/legacy/connect/proxy/socks/v4/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use super::super::{ParsingError, SerializeError};

use bytes::{Buf, BufMut};
use std::net::SocketAddrV4;

/// +-----+-----+----+----+----+----+----+----+-------------+------+------------+------+
/// | VN | CD | DSTPORT | DSTIP | USERID | NULL | DOMAIN | NULL |
/// +-----+-----+----+----+----+----+----+----+-------------+------+------------+------+
/// | 1 | 1 | 2 | 4 | Variable | 1 | Variable | 1 |
/// +-----+-----+----+----+----+----+----+----+-------------+------+------------+------+
/// ^^^^^^^^^^^^^^^^^^^^^
/// optional: only do IP is 0.0.0.X
#[derive(Debug)]
pub struct Request<'a>(pub &'a Address);

/// +-----+-----+----+----+----+----+----+----+
/// | VN | CD | DSTPORT | DSTIP |
/// +-----+-----+----+----+----+----+----+----+
/// | 1 | 1 | 2 | 4 |
/// +-----+-----+----+----+----+----+----+----+
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/// ignore: only for SOCKSv4 BIND
#[derive(Debug)]
pub struct Response(pub Status);

#[derive(Debug)]
pub enum Address {
Socket(SocketAddrV4),
Domain(String, u16),
}

#[derive(Debug, PartialEq)]
pub enum Status {
Success = 90,
Failed = 91,
IdentFailure = 92,
IdentMismatch = 93,
}

impl Request<'_> {
pub fn write_to_buf<B: BufMut>(&self, mut buf: B) -> Result<usize, SerializeError> {
match self.0 {
Address::Socket(socket) => {
if buf.remaining_mut() < 10 {
return Err(SerializeError::WouldOverflow);
}

buf.put_u8(0x04); // Version
buf.put_u8(0x01); // CONNECT

buf.put_u16(socket.port()); // Port
buf.put_slice(&socket.ip().octets()); // IP

buf.put_u8(0x00); // USERID
buf.put_u8(0x00); // NULL

Ok(10)
}

Address::Domain(domain, port) => {
if buf.remaining_mut() < 10 + domain.len() + 1 {
return Err(SerializeError::WouldOverflow);
}

buf.put_u8(0x04); // Version
buf.put_u8(0x01); // CONNECT

buf.put_u16(*port); // IP
buf.put_slice(&[0x00, 0x00, 0x00, 0xFF]); // Invalid IP

buf.put_u8(0x00); // USERID
buf.put_u8(0x00); // NULL

buf.put_slice(domain.as_bytes()); // Domain
buf.put_u8(0x00); // NULL

Ok(10 + domain.len() + 1)
}
}
}
}

impl TryFrom<&[u8]> for Response {
type Error = ParsingError;

fn try_from(mut buf: &[u8]) -> Result<Self, Self::Error> {
println!("===");
println!("{buf:?}");
println!("===");

if buf.remaining() < 8 {
return Err(ParsingError::Incomplete);
}

if buf.get_u8() != 0x04 {
return Err(ParsingError::Other);
}

let status = buf.get_u8().try_into()?;

let _addr = {
let port = buf.get_u16();
let mut ip = [0; 4];
buf.copy_to_slice(&mut ip);

SocketAddrV4::new(ip.into(), port)
};

return Ok(Self(status));
}
}

impl TryFrom<u8> for Status {
type Error = ParsingError;

fn try_from(byte: u8) -> Result<Self, Self::Error> {
Ok(match byte {
90 => Self::Success,
91 => Self::Failed,
92 => Self::IdentFailure,
93 => Self::IdentMismatch,
_ => return Err(ParsingError::Other),
})
}
}

impl std::fmt::Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Success => "success",
Self::Failed => "server failed to execute command",
Self::IdentFailure => "server ident service failed",
Self::IdentMismatch => "server ident service did not recognise client identifier",
})
}
}
Loading
Loading