-
-
Notifications
You must be signed in to change notification settings - Fork 117
Expand file tree
/
Copy pathsocks.rs
More file actions
248 lines (217 loc) · 7.68 KB
/
Copy pathsocks.rs
File metadata and controls
248 lines (217 loc) · 7.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
use std::{
borrow::Cow,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use http::Uri;
use pin_project_lite::pin_project;
use tokio_socks::{
TargetAddr,
tcp::{Socks4Stream, Socks5Stream},
};
use tower::Service;
use crate::core::{
client::connect::dns::{GaiResolver, Name, Resolve},
rt::{Read, TokioIo, Write},
};
#[derive(Debug)]
pub enum SocksError<C> {
Inner(C),
Socks(tokio_socks::Error),
Io(std::io::Error),
Utf8(std::str::Utf8Error),
DnsFailure,
MissingHost,
}
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::Socks(e) => f.write_fmt(format_args!("error during SOCKS handshake: {e}")),
Self::Io(e) => f.write_fmt(format_args!("io error during SOCKS handshake: {e}")),
Self::Utf8(e) => f.write_fmt(format_args!(
"invalid UTF-8 during SOCKS authentication: {e}"
)),
Self::DnsFailure => f.write_str("could not resolve to acceptable address type"),
Self::MissingHost => f.write_str("missing destination host"),
}
}
}
impl<C: std::fmt::Debug + std::fmt::Display> std::error::Error for SocksError<C> {}
impl<C> From<std::io::Error> for SocksError<C> {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl<C> From<std::str::Utf8Error> for SocksError<C> {
fn from(err: std::str::Utf8Error) -> Self {
Self::Utf8(err)
}
}
impl<C> From<tokio_socks::Error> for SocksError<C> {
fn from(err: tokio_socks::Error) -> Self {
Self::Socks(err)
}
}
/// Represents the SOCKS protocol version.
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum Version {
V4,
V5,
}
/// Represents the DNS resolution strategy for SOCKS connections.
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum DnsResolve {
Local,
Remote,
}
pin_project! {
// Not publicly exported (so missing_docs doesn't trigger).
//
// We return this `Future` instead of the `Pin<Box<dyn Future>>` directly
// so that users don't rely on it fitting in a `Pin<Box<dyn Future>>` slot
// (and thus we can change the type in the future).
#[must_use = "futures do nothing unless polled"]
pub struct Handshaking<F, T, E> {
#[pin]
fut: BoxHandshaking<T, E>,
_marker: std::marker::PhantomData<F>
}
}
type BoxHandshaking<T, E> = Pin<Box<dyn Future<Output = Result<T, SocksError<E>>> + Send>>;
impl<F, T, E> Future for Handshaking<F, T, E>
where
F: Future<Output = Result<T, E>>,
{
type Output = Result<T, SocksError<E>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().fut.poll(cx)
}
}
pub struct SocksConnector<C, R = GaiResolver> {
inner: C,
resolver: R,
proxy_dst: Uri,
auth: Option<(Bytes, Bytes)>,
version: Version,
dns_resolve: DnsResolve,
}
impl<C, R> SocksConnector<C, R>
where
R: Resolve + Clone,
{
/// Create a new SOCKS connector with the given inner service.
///
/// The wraps an underlying connector, and stores the address of a
/// SOCKS proxy server.
///
/// A `SocksConnector` can then be called with any destination. The `proxy_dst` passed to
/// `call` will not be used to create the underlying connection, but will
/// be used in a SOCKS handshake sent to the proxy destination.
pub fn new_with_resolver(proxy_dst: Uri, inner: C, resolver: R) -> Self {
SocksConnector {
inner,
resolver,
proxy_dst,
version: Version::V5,
dns_resolve: DnsResolve::Local,
auth: None,
}
}
/// Sets the authentication credentials for the SOCKS proxy connection.
pub fn with_auth(self, auth: Option<(Bytes, Bytes)>) -> Self {
SocksConnector { auth, ..self }
}
/// Sets whether to use the SOCKS5 protocol for the proxy connection.
pub fn with_version(self, version: Version) -> Self {
SocksConnector { version, ..self }
}
/// Sets whether to resolve DNS locally or let the proxy handle DNS resolution.
pub fn with_local_dns(self, dns_resolve: DnsResolve) -> Self {
SocksConnector {
dns_resolve,
..self
}
}
}
impl<C, R> Service<Uri> for SocksConnector<C, R>
where
C: Service<Uri>,
C::Future: Send + 'static,
C::Response: Read + Write + Unpin + Send + 'static,
C::Error: Send + Sync + 'static,
R: Resolve + Clone + Send + 'static,
<R as Resolve>::Future: Send + 'static,
{
type Response = C::Response;
type Error = SocksError<C::Error>;
type Future = Handshaking<C::Future, C::Response, C::Error>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(SocksError::Inner)
}
fn call(&mut self, dst: Uri) -> Self::Future {
let connecting = self.inner.call(self.proxy_dst.clone());
let version = self.version;
let dns_resolve = self.dns_resolve;
let auth = self.auth.clone();
let mut resolver = self.resolver.clone();
let fut = async move {
let port = dst.port().map(|p| p.as_u16()).unwrap_or(443);
let host = dst.host().ok_or(SocksError::MissingHost)?;
// Attempt to tcp connect to the proxy server.
// This will return a `tokio::net::TcpStream` if successful.
let socket = connecting
.await
.map(TokioIo::new)
.map_err(SocksError::Inner)?;
// Resolve the target address using the provided resolver.
let target_addr = match dns_resolve {
DnsResolve::Local => {
let mut socket_addr = resolver
.resolve(Name::new(host.into()))
.await
.map_err(|_| SocksError::DnsFailure)?
.next()
.ok_or(SocksError::DnsFailure)?;
socket_addr.set_port(port);
TargetAddr::Ip(socket_addr)
}
DnsResolve::Remote => TargetAddr::Domain(Cow::Borrowed(host), port),
};
match version {
Version::V4 => {
// For SOCKS4, we connect directly to the target address.
let stream = Socks4Stream::connect_with_socket(socket, target_addr).await?;
Ok(stream.into_inner().into_inner())
}
Version::V5 => {
// For SOCKS5, we need to handle authentication if provided.
// The `auth` is an optional tuple of (username, password).
let stream = match auth {
Some((username, password)) => {
let username = std::str::from_utf8(&username)?;
let password = std::str::from_utf8(&password)?;
Socks5Stream::connect_with_password_and_socket(
socket,
target_addr,
username,
password,
)
.await?
}
None => Socks5Stream::connect_with_socket(socket, target_addr).await?,
};
Ok(stream.into_inner().into_inner())
}
}
};
Handshaking {
fut: Box::pin(fut),
_marker: Default::default(),
}
}
}