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