1use crate::{
2 Layer, Service,
3 error::{BoxError, BoxErrorExt},
4 extensions::{Extensions, ExtensionsRef},
5 http::client::proxy::layer::{
6 HttpProxyConnector, HttpProxyConnectorLayer, MaybeHttpProxiedConnection,
7 },
8 io::Io,
9 net::{
10 AuthorityInputExt, HttpVersionInputExt, Protocol, ProtocolInputExt,
11 TargetHttpVersionInputExt,
12 client::{
13 ConnectionError, ConnectionErrorKind, ConnectorService, EstablishedClientConnection,
14 EstablishedProxyRoute, ProxyRoute,
15 },
16 },
17 proxy::socks5::{Socks5ProxyConnector, Socks5ProxyConnectorLayer},
18 telemetry::tracing,
19};
20use pin_project_lite::pin_project;
21use std::{
22 fmt::Debug,
23 pin::Pin,
24 task::{self, Poll},
25};
26use tokio::io::{AsyncRead, AsyncWrite};
27
28#[derive(Debug, Clone)]
33pub struct ProxyConnector<S> {
34 inner: S,
35 socks: Socks5ProxyConnector<S>,
36 http: HttpProxyConnector<S>,
37 required: bool,
38}
39
40impl<S: Clone> ProxyConnector<S> {
41 fn new(
43 inner: S,
44 socks_proxy_layer: Socks5ProxyConnectorLayer,
45 http_proxy_layer: HttpProxyConnectorLayer,
46 required: bool,
47 ) -> Self {
48 Self {
49 socks: socks_proxy_layer.into_layer(inner.clone()),
50 http: http_proxy_layer.into_layer(inner.clone()),
51 inner,
52 required,
53 }
54 }
55
56 #[inline]
57 pub fn required(
61 inner: S,
62 socks_proxy_layer: Socks5ProxyConnectorLayer,
63 http_proxy_layer: HttpProxyConnectorLayer,
64 ) -> Self {
65 Self::new(inner, socks_proxy_layer, http_proxy_layer, true)
66 }
67
68 #[inline]
69 pub fn optional(
73 inner: S,
74 socks_proxy_layer: Socks5ProxyConnectorLayer,
75 http_proxy_layer: HttpProxyConnectorLayer,
76 ) -> Self {
77 Self::new(inner, socks_proxy_layer, http_proxy_layer, false)
78 }
79}
80
81impl<Input, S> Service<Input> for ProxyConnector<S>
82where
83 S: ConnectorService<Input, Connection: Io + Unpin>,
84 Input: AuthorityInputExt
85 + ProtocolInputExt
86 + HttpVersionInputExt
87 + TargetHttpVersionInputExt
88 + Send
89 + ExtensionsRef
90 + 'static,
91{
92 type Output = EstablishedClientConnection<MaybeProxiedConnection<S::Connection>, Input>;
93 type Error = ConnectionError;
94
95 async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
96 let route = input.extensions().get_ref::<ProxyRoute>();
97 let route_requested = route.is_some();
98
99 match route {
100 None | Some(ProxyRoute::Direct) => {
101 if self.required {
102 return Err(ConnectionError::local(
103 BoxError::from_static_str("proxy required but none is defined"),
104 ConnectionErrorKind::InvalidInput,
105 ));
106 }
107 tracing::trace!("no proxy detected in ctx, using inner connector");
108 let EstablishedClientConnection { input, conn } = self.inner.connect(input).await?;
109
110 if route_requested {
111 conn.extensions().insert(EstablishedProxyRoute::Direct);
112 }
113
114 let conn = MaybeProxiedConnection::direct(conn);
115 Ok(EstablishedClientConnection { input, conn })
116 }
117 Some(ProxyRoute::Proxy(proxy)) => {
118 let protocol = proxy.protocol.as_ref();
119 tracing::trace!(?protocol, "proxy detected in ctx");
120
121 let protocol = protocol.unwrap_or_else(|| {
122 tracing::trace!("no protocol detected, using http as protocol");
123 &Protocol::HTTP
124 });
125
126 if protocol.is_socks5() {
127 tracing::trace!(
128 target = %&proxy.address,
129 "using socks proxy connector",
130 );
131
132 let EstablishedClientConnection { input, conn } =
133 self.socks.connect(input).await?;
134
135 let conn = MaybeProxiedConnection::socks(conn);
136 Ok(EstablishedClientConnection { input, conn })
137 } else if protocol.is_http() {
138 tracing::trace!(
139 target = %&proxy.address,
140 "using http proxy connector"
141 );
142
143 let EstablishedClientConnection { input, conn } =
144 self.http.connect(input).await?;
145
146 let conn = MaybeProxiedConnection::http(conn);
147 Ok(EstablishedClientConnection { input, conn })
148 } else {
149 Err(ConnectionError::transport(
150 BoxError::from_static_str("received unsupported proxy protocol"),
151 ConnectionErrorKind::Protocol,
152 )
153 .context_debug_field("protocol", protocol.clone()))
154 }
155 }
156 }
157 }
158}
159
160pin_project! {
161 pub struct MaybeProxiedConnection<S> {
163 #[pin]
164 inner: Connection<S>,
165 }
166}
167
168impl<S: ExtensionsRef> MaybeProxiedConnection<S> {
169 pub fn direct(conn: S) -> Self {
170 Self {
171 inner: Connection::Direct { conn },
172 }
173 }
174
175 pub fn socks(conn: S) -> Self {
176 Self {
177 inner: Connection::Socks { conn },
178 }
179 }
180
181 pub fn http(conn: MaybeHttpProxiedConnection<S>) -> Self {
182 Self {
183 inner: Connection::Http { conn },
184 }
185 }
186}
187
188impl<S: Debug> Debug for MaybeProxiedConnection<S> {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 f.debug_struct("MaybeProxiedConnection")
191 .field("inner", &self.inner)
192 .finish()
193 }
194}
195
196impl<S: ExtensionsRef> ExtensionsRef for MaybeProxiedConnection<S> {
197 fn extensions(&self) -> &Extensions {
198 match &self.inner {
199 Connection::Direct { conn } | Connection::Socks { conn } => conn.extensions(),
200 Connection::Http { conn } => conn.extensions(),
201 }
202 }
203}
204
205pin_project! {
206 #[project = ConnectionProj]
207 enum Connection<S> {
208 Direct{ #[pin] conn: S },
209 Socks{ #[pin] conn: S },
210 Http{ #[pin] conn: MaybeHttpProxiedConnection<S> },
211
212 }
213}
214
215impl<S: Debug> Debug for Connection<S> {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 Self::Direct { conn } => f.debug_struct("Direct").field("conn", conn).finish(),
219 Self::Socks { conn } => f.debug_struct("Socks").field("conn", conn).finish(),
220 Self::Http { conn } => f.debug_struct("Http").field("conn", conn).finish(),
221 }
222 }
223}
224
225#[warn(clippy::missing_trait_methods)]
226impl<Conn: AsyncWrite> AsyncWrite for MaybeProxiedConnection<Conn> {
227 fn poll_write(
228 self: Pin<&mut Self>,
229 cx: &mut task::Context<'_>,
230 buf: &[u8],
231 ) -> Poll<Result<usize, std::io::Error>> {
232 match self.project().inner.project() {
233 ConnectionProj::Direct { conn } | ConnectionProj::Socks { conn } => {
234 conn.poll_write(cx, buf)
235 }
236 ConnectionProj::Http { conn } => conn.poll_write(cx, buf),
237 }
238 }
239
240 fn poll_flush(
241 self: Pin<&mut Self>,
242 cx: &mut task::Context<'_>,
243 ) -> Poll<Result<(), std::io::Error>> {
244 match self.project().inner.project() {
245 ConnectionProj::Direct { conn } | ConnectionProj::Socks { conn } => conn.poll_flush(cx),
246 ConnectionProj::Http { conn } => conn.poll_flush(cx),
247 }
248 }
249
250 fn poll_shutdown(
251 self: Pin<&mut Self>,
252 cx: &mut task::Context<'_>,
253 ) -> Poll<Result<(), std::io::Error>> {
254 match self.project().inner.project() {
255 ConnectionProj::Direct { conn } | ConnectionProj::Socks { conn } => {
256 conn.poll_shutdown(cx)
257 }
258 ConnectionProj::Http { conn } => conn.poll_shutdown(cx),
259 }
260 }
261
262 fn is_write_vectored(&self) -> bool {
263 match &self.inner {
264 Connection::Direct { conn } | Connection::Socks { conn } => conn.is_write_vectored(),
265 Connection::Http { conn } => conn.is_write_vectored(),
266 }
267 }
268
269 fn poll_write_vectored(
270 self: Pin<&mut Self>,
271 cx: &mut task::Context<'_>,
272 bufs: &[std::io::IoSlice<'_>],
273 ) -> Poll<Result<usize, std::io::Error>> {
274 match self.project().inner.project() {
275 ConnectionProj::Direct { conn } | ConnectionProj::Socks { conn } => {
276 conn.poll_write_vectored(cx, bufs)
277 }
278 ConnectionProj::Http { conn } => conn.poll_write_vectored(cx, bufs),
279 }
280 }
281}
282
283#[warn(clippy::missing_trait_methods)]
284impl<Conn: AsyncRead> AsyncRead for MaybeProxiedConnection<Conn> {
285 fn poll_read(
286 self: Pin<&mut Self>,
287 cx: &mut task::Context<'_>,
288 buf: &mut tokio::io::ReadBuf<'_>,
289 ) -> Poll<std::io::Result<()>> {
290 match self.project().inner.project() {
291 ConnectionProj::Direct { conn } | ConnectionProj::Socks { conn } => {
292 conn.poll_read(cx, buf)
293 }
294 ConnectionProj::Http { conn } => conn.poll_read(cx, buf),
295 }
296 }
297}
298
299pub struct ProxyConnectorLayer {
304 socks_layer: Socks5ProxyConnectorLayer,
305 http_layer: HttpProxyConnectorLayer,
306 required: bool,
307}
308
309impl ProxyConnectorLayer {
310 #[must_use]
311 pub fn required(
315 socks_proxy_layer: Socks5ProxyConnectorLayer,
316 http_proxy_layer: HttpProxyConnectorLayer,
317 ) -> Self {
318 Self {
319 socks_layer: socks_proxy_layer,
320 http_layer: http_proxy_layer,
321 required: true,
322 }
323 }
324
325 #[must_use]
326 pub fn optional(
330 socks_proxy_layer: Socks5ProxyConnectorLayer,
331 http_proxy_layer: HttpProxyConnectorLayer,
332 ) -> Self {
333 Self {
334 socks_layer: socks_proxy_layer,
335 http_layer: http_proxy_layer,
336 required: false,
337 }
338 }
339}
340
341impl<S: Clone> Layer<S> for ProxyConnectorLayer {
342 type Service = ProxyConnector<S>;
343
344 fn layer(&self, inner: S) -> Self::Service {
345 ProxyConnector::new(
346 inner,
347 self.socks_layer.clone(),
348 self.http_layer.clone(),
349 self.required,
350 )
351 }
352
353 fn into_layer(self, inner: S) -> Self::Service {
354 ProxyConnector::new(inner, self.socks_layer, self.http_layer, self.required)
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::{
362 net::{proxy::IoForwardService, test_utils::client::MockSocket},
363 proxy::socks5::{
364 Socks5ProxyConnectorLayer,
365 server::{Connector as EagerSocks5Connector, Socks5Connector},
366 },
367 tcp::client::service::TcpConnector,
368 };
369
370 fn assert_socks5_connector<S, C: Socks5Connector<S>>(_: &C) {}
371
372 #[test]
373 fn eager_socks5_accepts_combined_proxy_connection() {
374 let proxy_connector = ProxyConnectorLayer::optional(
375 Socks5ProxyConnectorLayer::optional(),
376 HttpProxyConnectorLayer::optional(),
377 )
378 .into_layer(TcpConnector::new());
379 let connector = EagerSocks5Connector::new(proxy_connector, IoForwardService::default());
380
381 assert_socks5_connector::<MockSocket, _>(&connector);
382 }
383}