1use std::pin::Pin;
21use std::sync::Arc;
22use std::task::{Context, Poll};
23
24use bytes::Bytes;
25use http_body_util::{BodyExt, Empty, Limited};
26use hyper::body::Body;
27use hyper_util::rt::TokioIo;
28use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
29use url::Url;
30
31use crate::dns::Resolver;
32use crate::proxy::{OutboundProxies, ProxyTarget};
33
34const MAX_PROXY_ERROR_BYTES: usize = 512;
37
38pub(crate) const MAX_RESPONSE_BYTES: usize = 1024 * 1024;
46
47pub(crate) const MAX_ERROR_BODY_CHARS: usize = 200;
53
54pub(crate) fn error_excerpt(body: &[u8]) -> String {
61 String::from_utf8_lossy(body)
62 .chars()
63 .take(MAX_ERROR_BODY_CHARS)
64 .collect()
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub(crate) struct Endpoint {
70 pub host: String,
71 pub port: u16,
72 pub https: bool,
73}
74
75impl Endpoint {
76 pub(crate) fn from_url(url: &Url) -> Result<Self, String> {
83 let host = url
84 .host_str()
85 .ok_or_else(|| format!("{url} has no host"))?
86 .to_string();
87 let https = match url.scheme() {
88 "https" => true,
89 "http" => false,
90 other => return Err(format!("unsupported scheme: {other}")),
91 };
92 let port = url
93 .port_or_known_default()
94 .unwrap_or(if https { 443 } else { 80 });
95
96 Ok(Self { host, port, https })
97 }
98
99 pub(crate) fn tls(host: &str, port: u16) -> Self {
103 Self {
104 host: host.to_string(),
105 port,
106 https: true,
107 }
108 }
109
110 pub(crate) fn host_for_lookup(&self) -> &str {
118 self.host
119 .strip_prefix('[')
120 .and_then(|rest| rest.strip_suffix(']'))
121 .unwrap_or(&self.host)
122 }
123
124 pub(crate) fn authority(&self) -> String {
126 let default = if self.https { 443 } else { 80 };
127 if self.port == default {
128 self.host.clone()
129 } else {
130 format!("{}:{}", self.host, self.port)
131 }
132 }
133
134 pub(crate) fn connect_authority(&self) -> String {
142 format!("{}:{}", self.host, self.port)
143 }
144}
145
146pub(crate) fn webpki_tls_config() -> rustls::ClientConfig {
156 let roots = rustls::RootCertStore {
157 roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
158 };
159 rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
160 .with_safe_default_protocol_versions()
161 .expect("ring provider supports the default protocol versions")
162 .with_root_certificates(roots)
163 .with_no_client_auth()
164}
165
166pub(crate) enum ClientStream {
173 Direct(tokio::net::TcpStream),
174 Tunnelled(TokioIo<hyper::upgrade::Upgraded>),
175}
176
177impl AsyncRead for ClientStream {
178 fn poll_read(
179 self: Pin<&mut Self>,
180 context: &mut Context<'_>,
181 buffer: &mut ReadBuf<'_>,
182 ) -> Poll<std::io::Result<()>> {
183 match self.get_mut() {
184 Self::Direct(stream) => Pin::new(stream).poll_read(context, buffer),
185 Self::Tunnelled(stream) => Pin::new(stream).poll_read(context, buffer),
186 }
187 }
188}
189
190impl AsyncWrite for ClientStream {
191 fn poll_write(
192 self: Pin<&mut Self>,
193 context: &mut Context<'_>,
194 buffer: &[u8],
195 ) -> Poll<std::io::Result<usize>> {
196 match self.get_mut() {
197 Self::Direct(stream) => Pin::new(stream).poll_write(context, buffer),
198 Self::Tunnelled(stream) => Pin::new(stream).poll_write(context, buffer),
199 }
200 }
201
202 fn poll_flush(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<std::io::Result<()>> {
203 match self.get_mut() {
204 Self::Direct(stream) => Pin::new(stream).poll_flush(context),
205 Self::Tunnelled(stream) => Pin::new(stream).poll_flush(context),
206 }
207 }
208
209 fn poll_shutdown(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<std::io::Result<()>> {
210 match self.get_mut() {
211 Self::Direct(stream) => Pin::new(stream).poll_shutdown(context),
212 Self::Tunnelled(stream) => Pin::new(stream).poll_shutdown(context),
213 }
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub(crate) enum RequestForm {
220 Origin,
223 Absolute,
226}
227
228pub(crate) struct Connection<B> {
235 sender: hyper::client::conn::http1::SendRequest<B>,
236 form: RequestForm,
237 proxy_authorization: Option<String>,
240}
241
242impl<B> Connection<B>
243where
244 B: Body + 'static,
245{
246 pub(crate) fn request_target(&self, url: &Url) -> String {
248 match self.form {
249 RequestForm::Absolute => url.as_str().to_string(),
250 RequestForm::Origin => {
251 let mut target = url.path().to_string();
252 if let Some(query) = url.query() {
253 target.push('?');
254 target.push_str(query);
255 }
256 target
257 }
258 }
259 }
260
261 pub(crate) async fn send_request(
269 &mut self,
270 mut request: hyper::Request<B>,
271 ) -> hyper::Result<hyper::Response<hyper::body::Incoming>> {
272 if let Some(credential) = &self.proxy_authorization
273 && let Ok(value) = hyper::header::HeaderValue::from_str(credential)
274 {
275 request
276 .headers_mut()
277 .insert(hyper::header::PROXY_AUTHORIZATION, value);
278 }
279 self.sender.send_request(request).await
280 }
281}
282
283#[derive(Clone)]
298pub struct Outbound {
299 resolver: Arc<dyn Resolver>,
300 proxies: Arc<OutboundProxies>,
301}
302
303impl Outbound {
304 pub fn new(resolver: Arc<dyn Resolver>, proxies: Arc<OutboundProxies>) -> Self {
305 Self { resolver, proxies }
306 }
307
308 pub(crate) async fn connect<B>(
311 &self,
312 endpoint: &Endpoint,
313 tls: &Arc<rustls::ClientConfig>,
314 ) -> Result<Connection<B>, String>
315 where
316 B: Body + Send + 'static,
317 B::Data: Send,
318 B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
319 {
320 connect(self.resolver.as_ref(), &self.proxies, endpoint, tls).await
321 }
322
323 pub(crate) async fn connect_stream(&self, endpoint: &Endpoint) -> Result<ClientStream, String> {
326 connect_stream(self.resolver.as_ref(), &self.proxies, endpoint).await
327 }
328}
329
330pub(crate) async fn connect_stream(
337 resolver: &dyn Resolver,
338 proxies: &OutboundProxies,
339 endpoint: &Endpoint,
340) -> Result<ClientStream, String> {
341 match proxies.select(endpoint) {
342 Some(proxy) => tunnel(resolver, proxy, endpoint)
343 .await
344 .map(ClientStream::Tunnelled),
345 None => dial(resolver, endpoint).await.map(ClientStream::Direct),
346 }
347}
348
349pub(crate) async fn connect<B>(
375 resolver: &dyn Resolver,
376 proxies: &OutboundProxies,
377 endpoint: &Endpoint,
378 tls: &Arc<rustls::ClientConfig>,
379) -> Result<Connection<B>, String>
380where
381 B: Body + Send + 'static,
382 B::Data: Send,
383 B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
384{
385 let proxy = proxies.select(endpoint);
386
387 if let Some(proxy) = proxy
390 && !endpoint.https
391 {
392 let stream = dial(resolver, proxy.endpoint())
393 .await
394 .map_err(|error| format!("connecting to proxy {}: {error}", proxy.redacted()))?;
395 let sender = spawn_handshake(TokioIo::new(stream)).await?;
396 return Ok(Connection {
397 sender,
398 form: RequestForm::Absolute,
399 proxy_authorization: proxy.authorization().map(str::to_string),
400 });
401 }
402
403 let stream = match proxy {
404 Some(proxy) => ClientStream::Tunnelled(tunnel(resolver, proxy, endpoint).await?),
405 None => ClientStream::Direct(dial(resolver, endpoint).await?),
406 };
407
408 let sender = if endpoint.https {
409 let server_name =
412 rustls_pki_types::ServerName::try_from(endpoint.host_for_lookup().to_string())
413 .map_err(|error| format!("{}: {error}", endpoint.host))?;
414 let stream = tokio_rustls::TlsConnector::from(tls.clone())
415 .connect(server_name, stream)
416 .await
417 .map_err(|error| format!("TLS handshake with {}: {error}", endpoint.host))?;
418 spawn_handshake(TokioIo::new(stream)).await?
419 } else {
420 spawn_handshake(TokioIo::new(stream)).await?
421 };
422
423 Ok(Connection {
424 sender,
425 form: RequestForm::Origin,
426 proxy_authorization: None,
427 })
428}
429
430async fn dial(
432 resolver: &dyn Resolver,
433 endpoint: &Endpoint,
434) -> Result<tokio::net::TcpStream, String> {
435 crate::dns::connect(resolver, endpoint.host_for_lookup(), endpoint.port)
436 .await
437 .map_err(|error| format!("connecting to {}:{}: {error}", endpoint.host, endpoint.port))
438}
439
440async fn tunnel(
446 resolver: &dyn Resolver,
447 proxy: &ProxyTarget,
448 endpoint: &Endpoint,
449) -> Result<TokioIo<hyper::upgrade::Upgraded>, String> {
450 let socket = dial(resolver, proxy.endpoint())
451 .await
452 .map_err(|error| format!("connecting to proxy {}: {error}", proxy.redacted()))?;
453
454 let (mut sender, connection) = hyper::client::conn::http1::handshake(TokioIo::new(socket))
455 .await
456 .map_err(|error| format!("HTTP handshake with proxy {}: {error}", proxy.redacted()))?;
457
458 tokio::spawn(async move {
462 let _ = connection.with_upgrades().await;
463 });
464
465 let authority = endpoint.connect_authority();
466 let mut builder = hyper::Request::connect(&authority)
469 .header(hyper::header::HOST, &authority)
470 .header(hyper::header::USER_AGENT, "acme-proxy");
471 if let Some(credential) = proxy.authorization() {
472 builder = builder.header(hyper::header::PROXY_AUTHORIZATION, credential);
473 }
474 let request = builder
475 .body(Empty::<Bytes>::new())
476 .map_err(|error| format!("building the CONNECT request: {error}"))?;
477
478 let response = sender
479 .send_request(request)
480 .await
481 .map_err(|error| format!("CONNECT {authority} via {}: {error}", proxy.redacted()))?;
482
483 if !response.status().is_success() {
488 let status = response.status();
489 let excerpt = Limited::new(response.into_body(), MAX_PROXY_ERROR_BYTES)
490 .collect()
491 .await
492 .map(|body| {
493 String::from_utf8_lossy(&body.to_bytes())
494 .split_whitespace()
495 .collect::<Vec<_>>()
496 .join(" ")
497 .chars()
498 .take(200)
499 .collect::<String>()
500 })
501 .unwrap_or_default();
502 return Err(format!(
503 "proxy {} refused CONNECT {authority}: {status} {excerpt}",
504 proxy.redacted()
505 ));
506 }
507
508 hyper::upgrade::on(response)
509 .await
510 .map(TokioIo::new)
511 .map_err(|error| {
512 format!(
513 "proxy {} did not hand over the tunnel to {authority}: {error}",
514 proxy.redacted()
515 )
516 })
517}
518
519async fn spawn_handshake<B, I>(io: I) -> Result<hyper::client::conn::http1::SendRequest<B>, String>
522where
523 B: Body + Send + 'static,
524 B::Data: Send,
525 B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
526 I: hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static,
527{
528 let (sender, connection) = hyper::client::conn::http1::handshake(io)
529 .await
530 .map_err(|error| format!("HTTP handshake: {error}"))?;
531 tokio::spawn(async move {
532 let _ = connection.await;
535 });
536 Ok(sender)
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 fn url(value: &str) -> Url {
544 Url::parse(value).unwrap()
545 }
546
547 #[test]
548 fn default_ports_follow_the_scheme() {
549 let http = Endpoint::from_url(&url("http://example.com/x")).unwrap();
550 assert_eq!(http.port, 80);
551 assert!(!http.https);
552 assert_eq!(http.host, "example.com");
553
554 let https = Endpoint::from_url(&url("https://example.com/x")).unwrap();
555 assert_eq!(https.port, 443);
556 assert!(https.https);
557 }
558
559 #[test]
560 fn an_explicit_port_wins() {
561 let endpoint = Endpoint::from_url(&url("https://example.com:8443/x")).unwrap();
562 assert_eq!(endpoint.port, 8443);
563 assert!(endpoint.https);
564 }
565
566 #[test]
569 fn the_authority_omits_a_default_port() {
570 assert_eq!(
571 Endpoint::from_url(&url("https://example.com/x"))
572 .unwrap()
573 .authority(),
574 "example.com"
575 );
576 assert_eq!(
577 Endpoint::from_url(&url("http://example.com/x"))
578 .unwrap()
579 .authority(),
580 "example.com"
581 );
582 assert_eq!(
583 Endpoint::from_url(&url("https://example.com:8443/x"))
584 .unwrap()
585 .authority(),
586 "example.com:8443"
587 );
588 }
589
590 #[test]
591 fn a_url_with_no_host_is_refused() {
592 let error = Endpoint::from_url(&url("file:///etc/passwd")).unwrap_err();
593 assert!(
594 error.contains("no host") || error.contains("unsupported scheme"),
595 "{error}"
596 );
597 }
598
599 #[test]
600 fn an_unsupported_scheme_is_refused() {
601 let error = Endpoint::from_url(&url("ftp://example.com/x")).unwrap_err();
602 assert!(error.contains("unsupported scheme"), "{error}");
603 }
604
605 #[test]
606 fn an_ipv6_literal_survives_the_round_trip() {
607 let endpoint = Endpoint::from_url(&url("https://[2001:db8::1]:8443/x")).unwrap();
608 assert_eq!(endpoint.port, 8443);
609 assert_eq!(endpoint.authority(), "[2001:db8::1]:8443");
610 }
611
612 #[test]
616 fn an_ipv6_literal_loses_its_brackets_for_a_lookup() {
617 let endpoint = Endpoint::from_url(&url("https://[2001:db8::1]:8443/x")).unwrap();
618 assert_eq!(endpoint.host_for_lookup(), "2001:db8::1");
619 assert!(
620 endpoint
621 .host_for_lookup()
622 .parse::<std::net::IpAddr>()
623 .is_ok()
624 );
625
626 let named = Endpoint::from_url(&url("https://example.com/x")).unwrap();
627 assert_eq!(named.host_for_lookup(), "example.com");
628 }
629
630 #[test]
631 fn the_webpki_config_builds() {
632 let config = webpki_tls_config();
633 assert!(config.alpn_protocols.is_empty());
634 }
635
636 #[test]
640 fn a_connect_authority_always_carries_the_port() {
641 let https = Endpoint::from_url(&url("https://example.com/x")).unwrap();
642 assert_eq!(https.authority(), "example.com");
643 assert_eq!(https.connect_authority(), "example.com:443");
644
645 let http = Endpoint::from_url(&url("http://example.com/x")).unwrap();
646 assert_eq!(http.connect_authority(), "example.com:80");
647
648 let literal = Endpoint::from_url(&url("https://[2001:db8::1]/x")).unwrap();
649 assert_eq!(literal.connect_authority(), "[2001:db8::1]:443");
650 }
651
652 #[test]
653 fn an_endpoint_can_be_built_without_a_url() {
654 let endpoint = Endpoint::tls("example.com", 8443);
655 assert!(endpoint.https);
656 assert_eq!(endpoint.connect_authority(), "example.com:8443");
657 }
658
659 #[test]
662 fn the_request_target_follows_the_form() {
663 let target = url("http://example.com/a/b?c=d&e=f");
664 for (form, expected) in [
665 (RequestForm::Origin, "/a/b?c=d&e=f"),
666 (RequestForm::Absolute, "http://example.com/a/b?c=d&e=f"),
667 ] {
668 let connection = Connection::<Empty<Bytes>> {
669 sender: unreachable_sender(),
670 form,
671 proxy_authorization: None,
672 };
673 assert_eq!(connection.request_target(&target), expected);
674 }
675
676 let no_query = url("http://example.com/a");
677 let connection = Connection::<Empty<Bytes>> {
678 sender: unreachable_sender(),
679 form: RequestForm::Origin,
680 proxy_authorization: None,
681 };
682 assert_eq!(connection.request_target(&no_query), "/a");
683 }
684
685 fn unreachable_sender() -> hyper::client::conn::http1::SendRequest<Empty<Bytes>> {
688 let (sender, connection) = futures_lite_block_on(async {
689 let (client, _server) = tokio::io::duplex(64);
690 hyper::client::conn::http1::handshake(TokioIo::new(client))
691 .await
692 .unwrap()
693 });
694 drop(connection);
695 sender
696 }
697
698 fn futures_lite_block_on<F: Future>(future: F) -> F::Output {
701 tokio::runtime::Builder::new_current_thread()
702 .enable_all()
703 .build()
704 .unwrap()
705 .block_on(future)
706 }
707
708 mod loopback {
719 use super::*;
720 use crate::proxy::{OutboundProxies, ProxyTarget};
721 use crate::testutil::{FakeProxy, ProxyBehaviour};
722 use tokio::io::{AsyncReadExt, AsyncWriteExt};
723
724 struct UnreachableResolver;
728
729 #[async_trait::async_trait]
730 impl Resolver for UnreachableResolver {
731 async fn reverse(&self, _ip: std::net::IpAddr) -> Result<Vec<String>, String> {
732 unreachable!()
733 }
734 async fn forward(&self, _name: &str) -> Result<Vec<std::net::IpAddr>, String> {
735 unreachable!("a literal 127.0.0.1 must short-circuit before this is called")
736 }
737 async fn txt(&self, _name: &str) -> Result<Vec<String>, String> {
738 unreachable!()
739 }
740 }
741
742 struct LoopbackResolver;
745
746 #[async_trait::async_trait]
747 impl Resolver for LoopbackResolver {
748 async fn reverse(&self, _ip: std::net::IpAddr) -> Result<Vec<String>, String> {
749 unreachable!()
750 }
751 async fn forward(&self, _name: &str) -> Result<Vec<std::net::IpAddr>, String> {
752 Ok(vec![std::net::IpAddr::from([127, 0, 0, 1])])
753 }
754 async fn txt(&self, _name: &str) -> Result<Vec<String>, String> {
755 unreachable!()
756 }
757 }
758
759 fn through(proxy: &FakeProxy) -> OutboundProxies {
760 OutboundProxies::always(ProxyTarget::for_test(&proxy.url()))
761 }
762
763 fn tunnelling(port: u16) -> ProxyBehaviour {
764 ProxyBehaviour::Tunnel {
765 status: "HTTP/1.1 200 Connection established\r\n",
766 force_port: Some(port),
767 }
768 }
769
770 async fn origin(response: &'static str) -> u16 {
772 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
773 let port = listener.local_addr().unwrap().port();
774 tokio::spawn(async move {
775 let (mut stream, _) = listener.accept().await.unwrap();
776 let mut buffer = vec![0u8; 1024];
777 let _ = stream.read(&mut buffer).await;
778 let _ = stream.write_all(response.as_bytes()).await;
779 let _ = stream.shutdown().await;
780 });
781 port
782 }
783
784 #[tokio::test]
786 async fn connect_stream_tunnels_to_the_origin() {
787 let port = origin("pong").await;
788 let proxy = FakeProxy::start(tunnelling(port)).await;
789
790 let mut stream = connect_stream(
794 &LoopbackResolver,
795 &through(&proxy),
796 &Endpoint::tls("origin.example", 443),
797 )
798 .await
799 .expect("the tunnel must open");
800
801 stream.write_all(b"ping").await.unwrap();
802 let mut answer = String::new();
803 stream.read_to_string(&mut answer).await.unwrap();
804 assert_eq!(answer, "pong");
805
806 assert_eq!(proxy.connections(), 1);
807 let request = proxy.requests().remove(0);
808 assert!(
809 request.starts_with("CONNECT origin.example:443 HTTP/1.1"),
810 "{request}"
811 );
812 assert!(
815 !request.to_lowercase().contains("connection: close"),
816 "{request}"
817 );
818 assert!(
819 !request.to_lowercase().contains("proxy-connection"),
820 "{request}"
821 );
822 }
823
824 #[tokio::test]
827 async fn a_squid_shaped_reply_still_opens_the_tunnel() {
828 let port = origin("pong").await;
829 let proxy = FakeProxy::start(ProxyBehaviour::Tunnel {
830 status: "HTTP/1.0 200 Connection established\r\nProxy-Agent: squid/6.10\r\n",
831 force_port: Some(port),
832 })
833 .await;
834
835 let mut stream = connect_stream(
836 &LoopbackResolver,
837 &through(&proxy),
838 &Endpoint::tls("origin.example", 443),
839 )
840 .await
841 .expect("a 1.0 reply is still a tunnel");
842 stream.write_all(b"ping").await.unwrap();
843 let mut answer = String::new();
844 stream.read_to_string(&mut answer).await.unwrap();
845 assert_eq!(answer, "pong");
846 }
847
848 #[tokio::test]
851 async fn a_cleartext_target_is_forwarded_with_its_credentials() {
852 let proxy = FakeProxy::start(ProxyBehaviour::Forward(
853 "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok",
854 ))
855 .await;
856 let proxies = OutboundProxies::always(ProxyTarget::for_test(&format!(
857 "http://user:pass@127.0.0.1:{}",
858 proxy.port
859 )));
860
861 let target = Url::parse("http://origin.example/a?b=c").unwrap();
862 let endpoint = Endpoint::from_url(&target).unwrap();
863 let mut connection = connect::<Empty<Bytes>>(
864 &UnreachableResolver,
865 &proxies,
866 &endpoint,
867 &Arc::new(webpki_tls_config()),
868 )
869 .await
870 .expect("the proxy is the peer, so the origin need not exist");
871
872 let request = hyper::Request::builder()
873 .uri(connection.request_target(&target))
874 .header(hyper::header::HOST, endpoint.authority())
875 .body(Empty::<Bytes>::new())
876 .unwrap();
877 assert_eq!(
878 connection.send_request(request).await.unwrap().status(),
879 200
880 );
881
882 let seen = proxy.requests().remove(0);
883 assert!(
884 seen.starts_with("GET http://origin.example/a?b=c HTTP/1.1"),
885 "{seen}"
886 );
887 assert!(
888 seen.to_lowercase()
889 .contains("proxy-authorization: basic dxnlcjpwyxnz"),
890 "{seen}"
891 );
892 }
893
894 #[tokio::test]
898 async fn https_is_tunnelled_with_the_origin_s_own_sni() {
899 use rustls::server::{ClientHello, ResolvesServerCert};
900 use rustls::sign::CertifiedKey;
901 use std::sync::Mutex;
902
903 #[derive(Debug)]
904 struct RecordingCert {
905 key: Arc<CertifiedKey>,
906 names: Arc<Mutex<Vec<String>>>,
907 }
908
909 impl ResolvesServerCert for RecordingCert {
910 fn resolve(&self, hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
911 self.names
912 .lock()
913 .unwrap()
914 .push(hello.server_name().unwrap_or_default().to_string());
915 Some(self.key.clone())
916 }
917 }
918
919 let key_pair = rcgen::KeyPair::generate().unwrap();
923 let certificate = rcgen::CertificateParams::new(vec!["origin.example".to_string()])
924 .unwrap()
925 .self_signed(&key_pair)
926 .unwrap();
927 let provider = rustls::crypto::ring::default_provider();
928 let signing_key = provider
929 .key_provider
930 .load_private_key(
931 rustls_pki_types::PrivatePkcs8KeyDer::from(key_pair.serialize_der()).into(),
932 )
933 .unwrap();
934 let names = Arc::new(Mutex::new(Vec::new()));
935 let resolver = RecordingCert {
936 key: Arc::new(CertifiedKey::new(
937 vec![certificate.der().clone()],
938 signing_key,
939 )),
940 names: names.clone(),
941 };
942 let server_config = rustls::ServerConfig::builder_with_provider(Arc::new(provider))
943 .with_safe_default_protocol_versions()
944 .unwrap()
945 .with_no_client_auth()
946 .with_cert_resolver(Arc::new(resolver));
947
948 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
949 let origin_port = listener.local_addr().unwrap().port();
950 let seen_inside = Arc::new(Mutex::new(String::new()));
951 let recorder = seen_inside.clone();
952 tokio::spawn(async move {
953 let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config));
954 let (stream, _) = listener.accept().await.unwrap();
955 let mut stream = acceptor.accept(stream).await.unwrap();
956 let mut buffer = vec![0u8; 2048];
957 let read = stream.read(&mut buffer).await.unwrap();
958 *recorder.lock().unwrap() = String::from_utf8_lossy(&buffer[..read]).into_owned();
959 let _ = stream
960 .write_all(b"HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
961 .await;
962 let _ = stream.shutdown().await;
963 });
964
965 let proxy = FakeProxy::start(ProxyBehaviour::Tunnel {
969 status: "HTTP/1.1 200 Connection established\r\n",
970 force_port: Some(origin_port),
971 })
972 .await;
973 let proxies = OutboundProxies::always(ProxyTarget::for_test(&format!(
974 "http://user:pass@127.0.0.1:{}",
975 proxy.port
976 )));
977
978 let target = Url::parse("https://origin.example/x").unwrap();
979 let endpoint = Endpoint::from_url(&target).unwrap();
980 let mut connection = connect::<Empty<Bytes>>(
981 &LoopbackResolver,
982 &proxies,
983 &endpoint,
984 &crate::challenge::tls_alpn_01::accept_any_client_config(&[]).unwrap(),
985 )
986 .await
987 .expect("the tunnel must carry the TLS session");
988
989 let request = hyper::Request::builder()
990 .uri(connection.request_target(&target))
991 .header(hyper::header::HOST, endpoint.authority())
992 .body(Empty::<Bytes>::new())
993 .unwrap();
994 assert_eq!(
995 connection.send_request(request).await.unwrap().status(),
996 204
997 );
998
999 let connect_request = proxy.requests().remove(0);
1000 assert!(
1001 connect_request.starts_with("CONNECT origin.example:443 HTTP/1.1"),
1002 "{connect_request}"
1003 );
1004 assert!(
1005 connect_request
1006 .to_lowercase()
1007 .contains("proxy-authorization"),
1008 "{connect_request}"
1009 );
1010
1011 assert_eq!(names.lock().unwrap().as_slice(), ["origin.example"]);
1012
1013 let inside = seen_inside.lock().unwrap().clone();
1014 assert!(inside.starts_with("GET /x HTTP/1.1"), "{inside}");
1016 assert!(
1018 !inside.to_lowercase().contains("proxy-authorization"),
1019 "{inside}"
1020 );
1021 }
1022
1023 #[tokio::test]
1026 async fn a_refused_connect_reports_the_status_and_the_body() {
1027 let proxy = FakeProxy::start(ProxyBehaviour::Refuse(
1028 "HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 20\r\n\r\n\
1029 credentials required",
1030 ))
1031 .await;
1032 let proxies = OutboundProxies::always(ProxyTarget::for_test(&format!(
1033 "http://user:hunter2@127.0.0.1:{}",
1034 proxy.port
1035 )));
1036
1037 let Err(error) = connect_stream(
1038 &LoopbackResolver,
1039 &proxies,
1040 &Endpoint::tls("origin.example", 443),
1041 )
1042 .await
1043 else {
1044 panic!("a 407 is not a tunnel");
1045 };
1046
1047 assert!(error.contains("407"), "{error}");
1048 assert!(error.contains("credentials required"), "{error}");
1049 assert!(!error.contains("hunter2"), "{error}");
1052 }
1053
1054 #[tokio::test]
1057 async fn an_unreachable_proxy_names_the_proxy() {
1058 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1060 let port = listener.local_addr().unwrap().port();
1061 drop(listener);
1062
1063 let proxies =
1064 OutboundProxies::always(ProxyTarget::for_test(&format!("http://127.0.0.1:{port}")));
1065 let Err(error) = connect_stream(
1066 &LoopbackResolver,
1067 &proxies,
1068 &Endpoint::tls("origin.example", 443),
1069 )
1070 .await
1071 else {
1072 panic!("a dead proxy is not a tunnel");
1073 };
1074
1075 assert!(error.contains("proxy"), "{error}");
1076 assert!(error.contains(&port.to_string()), "{error}");
1077 assert!(!error.contains("origin.example"), "{error}");
1078 }
1079
1080 #[tokio::test]
1084 async fn a_bypassed_target_never_reaches_the_proxy() {
1085 let port = origin("pong").await;
1086 let proxy = FakeProxy::start(tunnelling(port)).await;
1087 let proxies = through(&proxy).with_bypass(&["bypassed.example"]).unwrap();
1088
1089 let mut stream = connect_stream(
1090 &LoopbackResolver,
1091 &proxies,
1092 &Endpoint::tls("bypassed.example", port),
1093 )
1094 .await
1095 .expect("a bypassed target still connects, just directly");
1096 stream.write_all(b"ping").await.unwrap();
1097 let mut answer = String::new();
1098 stream.read_to_string(&mut answer).await.unwrap();
1099 assert_eq!(answer, "pong");
1100
1101 assert_eq!(proxy.connections(), 0, "the proxy must not be dialled");
1102 }
1103 }
1104}