Skip to content

Commit 7e34375

Browse files
authored
feat(client): add proxy::SocksV4 and proxy::SocksV5 connectors (#187)
Adds SocksV4 and SocksV5 connectors with simple configuration for local and remote DNS. Supports SOCKSv5 user/pass authentication and allows optimistic message sending (not in RFC but supported by most servers) to reduce number of round-trips in SOCKSv5 handshake. cc hyperium/hyper#3851, #173 Closes hyperium/hyper#3877
1 parent 7e74248 commit 7e34375

File tree

9 files changed

+1610
-3
lines changed

9 files changed

+1610
-3
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Proxy helpers
2-
2+
mod socks;
33
mod tunnel;
44

5+
pub use self::socks::{SocksV4, SocksV5};
56
pub use self::tunnel::Tunnel;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
mod v5;
2+
pub use v5::{SocksV5, SocksV5Error};
3+
4+
mod v4;
5+
pub use v4::{SocksV4, SocksV4Error};
6+
7+
use bytes::BytesMut;
8+
9+
use hyper::rt::Read;
10+
11+
#[derive(Debug)]
12+
pub enum SocksError<C> {
13+
Inner(C),
14+
Io(std::io::Error),
15+
16+
DnsFailure,
17+
MissingHost,
18+
MissingPort,
19+
20+
V4(SocksV4Error),
21+
V5(SocksV5Error),
22+
23+
Parsing(ParsingError),
24+
Serialize(SerializeError),
25+
}
26+
27+
#[derive(Debug)]
28+
pub enum ParsingError {
29+
Incomplete,
30+
WouldOverflow,
31+
Other,
32+
}
33+
34+
#[derive(Debug)]
35+
pub enum SerializeError {
36+
WouldOverflow,
37+
}
38+
39+
async fn read_message<T, M, C>(mut conn: &mut T, buf: &mut BytesMut) -> Result<M, SocksError<C>>
40+
where
41+
T: Read + Unpin,
42+
M: for<'a> TryFrom<&'a mut BytesMut, Error = ParsingError>,
43+
{
44+
let mut tmp = [0; 513];
45+
46+
loop {
47+
let n = crate::rt::read(&mut conn, &mut tmp).await?;
48+
buf.extend_from_slice(&tmp[..n]);
49+
50+
match M::try_from(buf) {
51+
Err(ParsingError::Incomplete) => {
52+
if n == 0 {
53+
if buf.spare_capacity_mut().len() == 0 {
54+
return Err(SocksError::Parsing(ParsingError::WouldOverflow));
55+
} else {
56+
return Err(std::io::Error::new(
57+
std::io::ErrorKind::UnexpectedEof,
58+
"unexpected eof",
59+
)
60+
.into());
61+
}
62+
}
63+
}
64+
Err(err) => return Err(err.into()),
65+
Ok(res) => return Ok(res),
66+
}
67+
}
68+
}
69+
70+
impl<C> std::fmt::Display for SocksError<C> {
71+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72+
f.write_str("SOCKS error: ")?;
73+
74+
match self {
75+
Self::Inner(_) => f.write_str("failed to create underlying connection"),
76+
Self::Io(_) => f.write_str("io error during SOCKS handshake"),
77+
78+
Self::DnsFailure => f.write_str("could not resolve to acceptable address type"),
79+
Self::MissingHost => f.write_str("missing destination host"),
80+
Self::MissingPort => f.write_str("missing destination port"),
81+
82+
Self::Parsing(_) => f.write_str("failed parsing server response"),
83+
Self::Serialize(_) => f.write_str("failed serialize request"),
84+
85+
Self::V4(e) => e.fmt(f),
86+
Self::V5(e) => e.fmt(f),
87+
}
88+
}
89+
}
90+
91+
impl<C: std::fmt::Debug + std::fmt::Display> std::error::Error for SocksError<C> {}
92+
93+
impl<C> From<std::io::Error> for SocksError<C> {
94+
fn from(err: std::io::Error) -> Self {
95+
Self::Io(err)
96+
}
97+
}
98+
99+
impl<C> From<ParsingError> for SocksError<C> {
100+
fn from(err: ParsingError) -> Self {
101+
Self::Parsing(err)
102+
}
103+
}
104+
105+
impl<C> From<SerializeError> for SocksError<C> {
106+
fn from(err: SerializeError) -> Self {
107+
Self::Serialize(err)
108+
}
109+
}
110+
111+
impl<C> From<SocksV4Error> for SocksError<C> {
112+
fn from(err: SocksV4Error) -> Self {
113+
Self::V4(err)
114+
}
115+
}
116+
117+
impl<C> From<SocksV5Error> for SocksError<C> {
118+
fn from(err: SocksV5Error) -> Self {
119+
Self::V5(err)
120+
}
121+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use super::Status;
2+
3+
#[derive(Debug)]
4+
pub enum SocksV4Error {
5+
IpV6,
6+
Command(Status),
7+
}
8+
9+
impl From<Status> for SocksV4Error {
10+
fn from(err: Status) -> Self {
11+
Self::Command(err)
12+
}
13+
}
14+
15+
impl std::fmt::Display for SocksV4Error {
16+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+
match self {
18+
Self::IpV6 => f.write_str("IPV6 is not supported"),
19+
Self::Command(status) => status.fmt(f),
20+
}
21+
}
22+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use super::super::{ParsingError, SerializeError};
2+
3+
use bytes::{Buf, BufMut, BytesMut};
4+
use std::net::SocketAddrV4;
5+
6+
/// +-----+-----+----+----+----+----+----+----+-------------+------+------------+------+
7+
/// | VN | CD | DSTPORT | DSTIP | USERID | NULL | DOMAIN | NULL |
8+
/// +-----+-----+----+----+----+----+----+----+-------------+------+------------+------+
9+
/// | 1 | 1 | 2 | 4 | Variable | 1 | Variable | 1 |
10+
/// +-----+-----+----+----+----+----+----+----+-------------+------+------------+------+
11+
/// ^^^^^^^^^^^^^^^^^^^^^
12+
/// optional: only do IP is 0.0.0.X
13+
#[derive(Debug)]
14+
pub struct Request<'a>(pub &'a Address);
15+
16+
/// +-----+-----+----+----+----+----+----+----+
17+
/// | VN | CD | DSTPORT | DSTIP |
18+
/// +-----+-----+----+----+----+----+----+----+
19+
/// | 1 | 1 | 2 | 4 |
20+
/// +-----+-----+----+----+----+----+----+----+
21+
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22+
/// ignore: only for SOCKSv4 BIND
23+
#[derive(Debug)]
24+
pub struct Response(pub Status);
25+
26+
#[derive(Debug)]
27+
pub enum Address {
28+
Socket(SocketAddrV4),
29+
Domain(String, u16),
30+
}
31+
32+
#[derive(Debug, PartialEq)]
33+
pub enum Status {
34+
Success = 90,
35+
Failed = 91,
36+
IdentFailure = 92,
37+
IdentMismatch = 93,
38+
}
39+
40+
impl Request<'_> {
41+
pub fn write_to_buf<B: BufMut>(&self, mut buf: B) -> Result<usize, SerializeError> {
42+
match self.0 {
43+
Address::Socket(socket) => {
44+
if buf.remaining_mut() < 10 {
45+
return Err(SerializeError::WouldOverflow);
46+
}
47+
48+
buf.put_u8(0x04); // Version
49+
buf.put_u8(0x01); // CONNECT
50+
51+
buf.put_u16(socket.port()); // Port
52+
buf.put_slice(&socket.ip().octets()); // IP
53+
54+
buf.put_u8(0x00); // USERID
55+
buf.put_u8(0x00); // NULL
56+
57+
Ok(10)
58+
}
59+
60+
Address::Domain(domain, port) => {
61+
if buf.remaining_mut() < 10 + domain.len() + 1 {
62+
return Err(SerializeError::WouldOverflow);
63+
}
64+
65+
buf.put_u8(0x04); // Version
66+
buf.put_u8(0x01); // CONNECT
67+
68+
buf.put_u16(*port); // IP
69+
buf.put_slice(&[0x00, 0x00, 0x00, 0xFF]); // Invalid IP
70+
71+
buf.put_u8(0x00); // USERID
72+
buf.put_u8(0x00); // NULL
73+
74+
buf.put_slice(domain.as_bytes()); // Domain
75+
buf.put_u8(0x00); // NULL
76+
77+
Ok(10 + domain.len() + 1)
78+
}
79+
}
80+
}
81+
}
82+
83+
impl TryFrom<&mut BytesMut> for Response {
84+
type Error = ParsingError;
85+
86+
fn try_from(buf: &mut BytesMut) -> Result<Self, Self::Error> {
87+
if buf.remaining() < 8 {
88+
return Err(ParsingError::Incomplete);
89+
}
90+
91+
if buf.get_u8() != 0x00 {
92+
return Err(ParsingError::Other);
93+
}
94+
95+
let status = buf.get_u8().try_into()?;
96+
let _addr = {
97+
let port = buf.get_u16();
98+
let mut ip = [0; 4];
99+
buf.copy_to_slice(&mut ip);
100+
101+
SocketAddrV4::new(ip.into(), port)
102+
};
103+
104+
return Ok(Self(status));
105+
}
106+
}
107+
108+
impl TryFrom<u8> for Status {
109+
type Error = ParsingError;
110+
111+
fn try_from(byte: u8) -> Result<Self, Self::Error> {
112+
Ok(match byte {
113+
90 => Self::Success,
114+
91 => Self::Failed,
115+
92 => Self::IdentFailure,
116+
93 => Self::IdentMismatch,
117+
_ => return Err(ParsingError::Other),
118+
})
119+
}
120+
}
121+
122+
impl std::fmt::Display for Status {
123+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124+
f.write_str(match self {
125+
Self::Success => "success",
126+
Self::Failed => "server failed to execute command",
127+
Self::IdentFailure => "server ident service failed",
128+
Self::IdentMismatch => "server ident service did not recognise client identifier",
129+
})
130+
}
131+
}

0 commit comments

Comments
 (0)