1use std::{
2 collections::HashMap,
3 io,
4 sync::Arc,
5};
6#[cfg(feature = "hyper")]
7use std::{
8 convert::Infallible,
9 fmt,
10};
11
12use async_trait::async_trait;
13use bitflags::bitflags;
14use futures::stream::TryStreamExt;
15use futures_rustls::rustls::{
16 self,
17 ClientConfig,
18 pki_types,
19};
20#[cfg(feature = "hyper")]
21use hyper::{
22 Response,
23 StatusCode,
24 server::conn::http1,
25 service::service_fn,
26};
27use once_cell::sync::Lazy;
28use proxy_protocol::ProxyHeader;
29use tokio::{
30 io::copy_bidirectional,
31 net::TcpStream,
32 task::JoinHandle,
33};
34use tokio_util::compat::{
35 FuturesAsyncReadCompatExt,
36 TokioAsyncReadCompatExt,
37};
38#[cfg(feature = "hyper")]
39use tracing::debug;
40use tracing::{
41 Instrument,
42 Span,
43 field,
44 warn,
45};
46use url::Url;
47
48use crate::{
49 EdgeConn,
50 EndpointConn,
51 prelude::*,
52 proxy_proto,
53 session::IoStream,
54};
55
56#[allow(deprecated)]
57#[async_trait]
58impl<T> TunnelExt for T
59where
60 T: Tunnel + Send,
61 <T as Tunnel>::Conn: ConnExt,
62{
63 async fn forward(&mut self, url: Url) -> Result<(), io::Error> {
64 forward_tunnel(self, url).await
65 }
66}
67
68#[async_trait]
70#[deprecated = "superceded by the `listen_and_forward` builder method"]
71pub trait TunnelExt: Tunnel + Send {
72 async fn forward(&mut self, url: Url) -> Result<(), io::Error>;
87}
88
89pub(crate) trait ConnExt {
90 fn forward_to(self, url: &Url) -> JoinHandle<io::Result<()>>;
91}
92
93#[tracing::instrument(skip_all, fields(tunnel_id = tun.id(), url = %url))]
94pub(crate) async fn forward_tunnel<T>(tun: &mut T, url: Url) -> Result<(), io::Error>
95where
96 T: Tunnel + 'static + ?Sized,
97 <T as Tunnel>::Conn: ConnExt,
98{
99 loop {
100 let tunnel_conn = match tun
101 .try_next()
102 .await
103 .map_err(|err| io::Error::new(io::ErrorKind::NotConnected, err))?
104 {
105 Some(conn) => conn,
106 _ => {
107 return Ok(());
108 }
109 };
110
111 tunnel_conn.forward_to(&url);
112 }
113}
114
115impl ConnExt for EdgeConn {
116 fn forward_to(mut self, url: &Url) -> JoinHandle<io::Result<()>> {
117 let url = url.clone();
118 tokio::spawn(async move {
119 let mut upstream = match connect(
120 self.edge_type() == EdgeType::Tls && self.passthrough_tls(),
121 self.inner.info.verify_upstream_tls,
122 self.inner.info.app_protocol.clone(),
123 None, &url,
125 )
126 .await
127 {
128 Ok(conn) => conn,
129 Err(error) => {
130 #[cfg(feature = "hyper")]
131 if self.edge_type() == EdgeType::Https {
132 serve_gateway_error(format!("{error}"), self);
133 }
134 warn!(%error, "error connecting to upstream");
135 return Err(error);
136 }
137 };
138
139 copy_bidirectional(&mut self, &mut upstream).await?;
140 Ok(())
141 })
142 }
143}
144
145impl ConnExt for EndpointConn {
146 fn forward_to(self, url: &Url) -> JoinHandle<Result<(), io::Error>> {
147 let url = url.clone();
148 tokio::spawn(async move {
149 let proxy_proto = self.inner.info.proxy_proto;
150 let proto_tls = self.proto() == "tls";
151 #[cfg(feature = "hyper")]
152 let proto_http = matches!(self.proto(), "http" | "https");
153 let passthrough_tls = self.inner.info.passthrough_tls();
154 let app_protocol = self.inner.info.app_protocol.clone();
155 let verify_upstream_tls = self.inner.info.verify_upstream_tls;
156
157 let (mut stream, proxy_header) = match proxy_proto {
158 ProxyProto::None => (crate::proxy_proto::Stream::disabled(self), None),
159 _ => {
160 let mut stream = crate::proxy_proto::Stream::incoming(self);
161 let header = stream
162 .proxy_header()
163 .await?
164 .map_err(|e| {
165 io::Error::new(
166 io::ErrorKind::InvalidData,
167 format!("invalid proxy-protocol header: {}", e),
168 )
169 })?
170 .cloned();
171 (stream, header)
172 }
173 };
174
175 let mut upstream = match connect(
176 proto_tls && passthrough_tls,
177 verify_upstream_tls,
178 app_protocol,
179 proxy_header,
180 &url,
181 )
182 .await
183 {
184 Ok(conn) => conn,
185 Err(error) => {
186 #[cfg(feature = "hyper")]
187 if proto_http {
188 serve_gateway_error(format!("{error}"), stream);
189 }
190 warn!(%error, "error connecting to upstream");
191 return Err(error);
192 }
193 };
194
195 copy_bidirectional(&mut stream, &mut upstream).await?;
196 Ok(())
197 })
198 }
199}
200
201bitflags! {
202 struct TlsFlags: u8 {
203 const FLAG_HTTP2 = 0b01;
204 const FLAG_verify_upstream_tls = 0b10;
205 const FLAG_MAX = Self::FLAG_HTTP2.bits()
206 | Self::FLAG_verify_upstream_tls.bits();
207 }
208}
209
210static NO_CRYPTO_PROVIDER_ERROR: Lazy<io::Error> = Lazy::new(|| {
211 io::Error::new(
212 io::ErrorKind::NotFound,
213 "no default CryptoProvider installed",
214 )
215});
216
217fn tls_config(
218 app_protocol: Option<String>,
219 verify_upstream_tls: bool,
220) -> Result<Arc<ClientConfig>, &'static io::Error> {
221 #[allow(clippy::type_complexity)]
228 static CONFIGS: Lazy<Result<HashMap<u8, Arc<ClientConfig>>, &'static io::Error>> =
229 Lazy::new(|| {
230 std::ops::Range {
231 start: 0,
232 end: TlsFlags::FLAG_MAX.bits() + 1,
233 }
234 .map(|p| {
235 let http2 = (p & TlsFlags::FLAG_HTTP2.bits()) != 0;
236 let verify_upstream_tls = (p & TlsFlags::FLAG_verify_upstream_tls.bits()) != 0;
237 let mut config = crate::session::host_certs_tls_config()?;
238 if !verify_upstream_tls {
239 let provider = rustls::crypto::CryptoProvider::get_default()
240 .ok_or(&*NO_CRYPTO_PROVIDER_ERROR)?
241 .as_ref()
242 .clone();
243 config.dangerous().set_certificate_verifier(Arc::new(
244 danger::NoCertificateVerification::new(provider),
245 ));
246 }
247
248 if http2 {
249 config
250 .alpn_protocols
251 .extend(["h2", "http/1.1"].iter().map(|s| s.as_bytes().to_vec()));
252 }
253 Ok((p, Arc::new(config)))
254 })
255 .collect()
256 });
257
258 let configs: &HashMap<u8, Arc<ClientConfig>> = CONFIGS.as_ref().map_err(|e| *e)?;
259 let mut key = 0;
260 if Some("http2").eq(&app_protocol.as_deref()) {
261 key |= TlsFlags::FLAG_HTTP2.bits();
262 }
263 if verify_upstream_tls {
264 key |= TlsFlags::FLAG_verify_upstream_tls.bits();
265 }
266
267 Ok(configs
268 .get(&key)
269 .or_else(|| configs.get(&0))
270 .unwrap()
271 .clone())
272}
273
274async fn connect(
279 tunnel_tls: bool,
280 verify_upstream_tls: bool,
281 app_protocol: Option<String>,
282 proxy_proto_header: Option<ProxyHeader>,
283 url: &Url,
284) -> Result<Box<dyn IoStream>, io::Error> {
285 let host = url.host_str().unwrap_or("localhost");
286 let mut backend_tls: bool = false;
287 let mut conn: Box<dyn IoStream> = match url.scheme() {
288 "tcp" => {
289 let port = url.port().ok_or_else(|| {
290 io::Error::new(
291 io::ErrorKind::InvalidInput,
292 format!("missing port for tcp forwarding url {url}"),
293 )
294 })?;
295 let conn = connect_tcp(host, port).in_current_span().await?;
296 Box::new(conn)
297 }
298
299 "http" => {
300 let port = url.port().unwrap_or(80);
301 let conn = connect_tcp(host, port).in_current_span().await?;
302 Box::new(conn)
303 }
304
305 "https" | "tls" => {
306 let port = url.port().unwrap_or(443);
307 let conn = connect_tcp(host, port).in_current_span().await?;
308
309 backend_tls = true;
310 Box::new(conn)
311 }
312
313 #[cfg(not(target_os = "windows"))]
314 "unix" => {
315 use std::borrow::Cow;
316
317 use tokio::net::UnixStream;
318
319 let mut addr = Cow::Borrowed(url.path());
320 if let Some(host) = url.host_str() {
321 addr = Cow::Owned(format!("{host}{addr}"));
324 }
325 Box::new(UnixStream::connect(&*addr).await?)
326 }
327
328 #[cfg(target_os = "windows")]
329 "pipe" => {
330 use std::time::Duration;
331
332 use tokio::net::windows::named_pipe::ClientOptions;
333 use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
334
335 let mut pipe_name = url.path();
336 if url.host_str().is_some() {
337 pipe_name = pipe_name.strip_prefix('/').unwrap_or(pipe_name);
338 }
339 if pipe_name.is_empty() {
340 return Err(io::Error::new(
341 io::ErrorKind::InvalidInput,
342 format!("missing pipe name in forwarding url {url}"),
343 ));
344 }
345 let host = url
346 .host_str()
347 .map(|h| if h == "localhost" { "." } else { h })
349 .unwrap_or(".");
350 let addr = format!("\\\\{host}\\pipe\\{pipe_name}");
352 let local_conn = loop {
355 match ClientOptions::new().open(&addr) {
356 Ok(client) => break client,
357 Err(error) if error.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
358 Err(error) => return Err(error),
359 }
360
361 tokio::time::sleep(Duration::from_millis(50)).await;
362 };
363 Box::new(local_conn)
364 }
365 _ => {
366 return Err(io::Error::new(
367 io::ErrorKind::InvalidInput,
368 format!("unrecognized scheme in forwarding url: {url}"),
369 ));
370 }
371 };
372
373 if let Some(header) = proxy_proto_header {
375 conn = Box::new(
376 proxy_proto::Stream::outgoing(conn, header)
377 .expect("re-serializing proxy header should always succeed"),
378 )
379 };
380
381 if backend_tls && !tunnel_tls {
382 let domain = pki_types::ServerName::try_from(host)
383 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
384 .to_owned();
385 conn = Box::new(
386 futures_rustls::TlsConnector::from(
387 tls_config(app_protocol, verify_upstream_tls).map_err(|e| e.kind())?,
388 )
389 .connect(domain, conn.compat())
390 .await?
391 .compat(),
392 )
393 }
394
395 Ok(conn)
398}
399
400async fn connect_tcp(host: &str, port: u16) -> Result<TcpStream, io::Error> {
401 let conn = TcpStream::connect(&format!("{}:{}", host, port)).await?;
402 if let Ok(addr) = conn.peer_addr() {
403 Span::current().record("forward_addr", field::display(addr));
404 }
405 Ok(conn)
406}
407
408#[cfg(feature = "hyper")]
409fn serve_gateway_error(
410 err: impl fmt::Display + Send + 'static,
411 conn: impl hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static,
412) -> JoinHandle<()> {
413 tokio::spawn(
414 async move {
415 let service = service_fn(move |_req| {
416 debug!("serving bad gateway error");
417 let mut resp = Response::new(format!("failed to dial backend: {err}"));
418 *resp.status_mut() = StatusCode::BAD_GATEWAY;
419 futures::future::ok::<_, Infallible>(resp)
420 });
421
422 let res = http1::Builder::new()
423 .keep_alive(false)
424 .serve_connection(conn, service)
425 .await;
426 debug!(?res, "connection closed");
427 }
428 .in_current_span(),
429 )
430}
431
432mod danger {
434 use futures_rustls::rustls;
435 use rustls::{
436 DigitallySignedStruct,
437 client::danger::HandshakeSignatureValid,
438 crypto::{
439 CryptoProvider,
440 verify_tls12_signature,
441 verify_tls13_signature,
442 },
443 };
444
445 use super::pki_types::{
446 CertificateDer,
447 ServerName,
448 UnixTime,
449 };
450
451 #[derive(Debug)]
452 pub struct NoCertificateVerification(CryptoProvider);
453
454 impl NoCertificateVerification {
455 pub fn new(provider: CryptoProvider) -> Self {
456 Self(provider)
457 }
458 }
459
460 impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
461 fn verify_server_cert(
462 &self,
463 _end_entity: &CertificateDer<'_>,
464 _intermediates: &[CertificateDer<'_>],
465 _server_name: &ServerName<'_>,
466 _ocsp: &[u8],
467 _now: UnixTime,
468 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
469 Ok(rustls::client::danger::ServerCertVerified::assertion())
470 }
471
472 fn verify_tls12_signature(
473 &self,
474 message: &[u8],
475 cert: &CertificateDer<'_>,
476 dss: &DigitallySignedStruct,
477 ) -> Result<HandshakeSignatureValid, rustls::Error> {
478 verify_tls12_signature(
479 message,
480 cert,
481 dss,
482 &self.0.signature_verification_algorithms,
483 )
484 }
485
486 fn verify_tls13_signature(
487 &self,
488 message: &[u8],
489 cert: &CertificateDer<'_>,
490 dss: &DigitallySignedStruct,
491 ) -> Result<HandshakeSignatureValid, rustls::Error> {
492 verify_tls13_signature(
493 message,
494 cert,
495 dss,
496 &self.0.signature_verification_algorithms,
497 )
498 }
499
500 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
501 self.0.signature_verification_algorithms.supported_schemes()
502 }
503 }
504}