1use crate::auth::ProxyAuthorization;
4use crate::backend::{
5 BackendError, CancellationToken, ProxyBackend, ProxyConnectOpen, ProxyConnectTunnel,
6 ProxyHeader, ProxyRequest, ProxyRequestBody, ProxyResponse, ProxyResponseBody, ProxyTunnel,
7 ProxyTunnelOpen,
8};
9use crate::certificate::LocalTlsIdentityStore;
10use crate::config::{ProxyConfig, ProxyLimits, ProxyRoutingMode, ProxyTimeouts};
11use crate::endpoint::ProxyEndpoint;
12use crate::event::{
13 BackendFailureKind, ClientRejectionReason, LifecycleEvent, ObservedHost, ObservedMethod,
14 ProxyEvent, ProxyObserver, RequestPhase, RequestRejectionReason, StopReason,
15};
16use crate::host::HostScopeError;
17use crate::http1::{
18 AbsoluteTarget, Authority, BodyFraming, Http1Error, RequestHead, RequestTarget, Scheme,
19 determine_body_framing, read_request_body, read_request_head, sanitize_forward_headers,
20 sanitize_upgrade_forward_headers,
21};
22use crate::listener::{
23 ClientHandler, ListenerExitHandler, ListenerExitPhase, ListenerExitReason, OwnedListener,
24 RejectionHandler,
25};
26use crate::metadata::{
27 NoopProxyResponseMetadataObserver, ProxyMetadataObservationKind,
28 ProxyResponseMetadataObservation, ProxyResponseMetadataObserver,
29};
30use crate::rate_limit::{
31 RateLimitConfig, RateLimitConfigError, RateLimitDecision, RateLimitScope, RequestRateLimiter,
32};
33use crate::response::{
34 encode_response_head, encode_upgrade_response_head, sanitize_response_headers,
35};
36use crate::tls::{TlsStream, accept_local_tls};
37use hns_core::network_policy::{is_browser_blocked_port, is_browser_special_use_host};
38use std::fmt;
39use std::io::{self, Read, Write};
40use std::net::{Shutdown, SocketAddr, TcpStream};
41use std::panic::{AssertUnwindSafe, catch_unwind};
42use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
43use std::sync::{Arc, Condvar, Mutex as StdMutex};
44use std::thread::{self, ThreadId};
45use std::time::{Duration, Instant};
46use thiserror::Error;
47
48const RESPONSE_COPY_BUFFER_BYTES: usize = 16 * 1024;
49const TUNNEL_COPY_BUFFER_BYTES: usize = 16 * 1024;
50const TUNNEL_CLIENT_POLL_INTERVAL: Duration = Duration::from_millis(25);
51const CONNECT_TUNNEL_IO_TIMEOUT: Duration = Duration::from_millis(250);
52const CONNECT_ESTABLISHED_RESPONSE: &[u8] = b"HTTP/1.1 200 Connection Established\r\n\r\n";
53
54#[derive(Debug, Error)]
57pub enum ProxyError {
58 #[error("unable to generate mandatory loopback proxy credentials")]
59 Authorization(#[source] crate::AuthorizationGenerationError),
60 #[error("invalid loopback proxy rate-limit configuration")]
61 RateLimit(#[source] RateLimitConfigError),
62 #[error("unable to start the IPv4 loopback proxy listener")]
63 Listener(#[source] io::Error),
64}
65
66pub struct RunningProxy {
68 endpoint: ProxyEndpoint,
69 listener: OwnedListener,
70 tls_identities: Arc<LocalTlsIdentityStore>,
71 observer: Arc<dyn ProxyObserver>,
72 generation: u64,
73 stop_state: Arc<ProxyStopState>,
74}
75
76impl RunningProxy {
77 pub fn start(
80 config: ProxyConfig,
81 backend: Arc<dyn ProxyBackend>,
82 observer: Arc<dyn ProxyObserver>,
83 ) -> Result<Self, ProxyError> {
84 Self::start_with_metadata_observer(
85 config,
86 backend,
87 observer,
88 Arc::new(NoopProxyResponseMetadataObserver),
89 )
90 }
91
92 pub fn start_with_metadata_observer(
95 config: ProxyConfig,
96 backend: Arc<dyn ProxyBackend>,
97 observer: Arc<dyn ProxyObserver>,
98 metadata_observer: Arc<dyn ProxyResponseMetadataObserver>,
99 ) -> Result<Self, ProxyError> {
100 let authorization =
101 Arc::new(ProxyAuthorization::generate().map_err(ProxyError::Authorization)?);
102 let limits = config.limits();
103 let timeouts = config.timeouts();
104 let generation = config.instance().generation();
105 let rate_limiter =
106 RequestRateLimiter::new(rate_limit_config(limits)).map_err(ProxyError::RateLimit)?;
107 let cancellation = CancellationToken::new();
108 let tls_identities = Arc::new(LocalTlsIdentityStore::with_certificate_authority(
109 config.instance().clone(),
110 config.local_certificate_authority().cloned(),
111 ));
112 let stop_state = Arc::new(ProxyStopState::new());
113 let context = Arc::new(ServerContext {
114 authorization: Arc::clone(&authorization),
115 hns_scope: config.hns_scope().cloned(),
116 routing_mode: config.routing_mode(),
117 limits,
118 timeouts,
119 backend,
120 observer: Arc::clone(&observer),
121 metadata_observer,
122 generation,
123 rate_limiter,
124 tls_identities: Arc::clone(&tls_identities),
125 });
126
127 let client_context = Arc::clone(&context);
128 let client_handler: Arc<ClientHandler> = Arc::new(move |stream, peer, token| {
129 handle_client(stream, peer, token, &client_context);
130 });
131 let rejection_observer = Arc::clone(&observer);
132 let rejection_handler: Arc<RejectionHandler> = Arc::new(move |mut stream, _, _| {
133 let _result = stream.set_write_timeout(Some(timeouts.socket_timeout()));
134 observe(
135 rejection_observer.as_ref(),
136 ProxyEvent::ClientRejected {
137 generation,
138 reason: ClientRejectionReason::ActiveClientLimit,
139 },
140 );
141 let _result = write_error_response(
142 &mut stream,
143 429,
144 "Too Many Requests",
145 &[("Retry-After", "1".to_owned())],
146 );
147 let _result = stream.shutdown(Shutdown::Both);
148 });
149 let exit_handler = listener_exit_handler(
150 Arc::clone(&tls_identities),
151 Arc::clone(&observer),
152 generation,
153 Arc::clone(&stop_state),
154 );
155
156 let listener = OwnedListener::start(
157 SocketAddr::V4(config.bind().socket_addr()),
158 limits.max_active_clients(),
159 cancellation,
160 client_handler,
161 rejection_handler,
162 exit_handler,
163 )
164 .map_err(ProxyError::Listener)?;
165 let endpoint = ProxyEndpoint::new(
166 config.instance().clone(),
167 listener.local_addr(),
168 authorization,
169 );
170 observe(
171 observer.as_ref(),
172 ProxyEvent::Lifecycle {
173 generation,
174 event: LifecycleEvent::Listening {
175 port: endpoint.port(),
176 },
177 },
178 );
179 stop_state.publish_listening();
180
181 Ok(Self {
182 endpoint,
183 listener,
184 tls_identities,
185 observer,
186 generation,
187 stop_state,
188 })
189 }
190
191 pub fn endpoint(&self) -> &ProxyEndpoint {
192 &self.endpoint
193 }
194
195 pub fn active_clients(&self) -> usize {
196 self.listener.active_clients()
197 }
198
199 pub fn is_stopped(&self) -> bool {
200 self.stop_state.completed.load(Ordering::Acquire)
201 }
202
203 pub fn is_stop_requested(&self) -> bool {
204 self.stop_state.requested.load(Ordering::Acquire)
205 }
206
207 pub fn matches_authentication_challenge(&self, host: &str, port: u16, realm: &str) -> bool {
210 !self.is_stop_requested()
211 && self.endpoint.matches_challenge(host, port, realm)
212 && !self.is_stop_requested()
213 }
214
215 pub fn local_certificate_pin(&self, host: &str) -> Option<crate::LocalCertificatePin> {
220 if self.stop_state.requested.load(Ordering::Acquire) {
221 return None;
222 }
223 let host = crate::NormalizedHost::parse(host).ok()?;
224 self.tls_identities.pin(&host)
225 }
226
227 pub fn matches_local_certificate(&self, host: &str, certificate_der: &[u8]) -> bool {
230 if self.stop_state.requested.load(Ordering::Acquire) {
231 return false;
232 }
233 let Ok(host) = crate::NormalizedHost::parse(host) else {
234 return false;
235 };
236 self.tls_identities.matches_der(&host, certificate_der)
237 && !self.stop_state.requested.load(Ordering::Acquire)
238 }
239
240 pub fn request_stop(&self) {
245 self.stop_state.requested.store(true, Ordering::Release);
246 self.listener.request_stop();
247 self.tls_identities.deactivate();
248 }
249
250 pub fn stop(&self) {
255 self.request_stop();
256 if !self
257 .stop_state
258 .announcement
259 .announce(self.observer.as_ref(), self.generation)
260 {
261 return;
262 }
263 let joined = self.listener.stop();
264 if joined {
265 self.stop_state.complete(
266 self.observer.as_ref(),
267 self.generation,
268 StopReason::Requested,
269 );
270 }
271 }
272}
273
274struct ProxyStopState {
275 requested: AtomicBool,
276 completed: AtomicBool,
277 announcement: StopAnnouncement,
278 listening_published: StdMutex<bool>,
279 listening_changed: Condvar,
280}
281
282impl ProxyStopState {
283 fn new() -> Self {
284 Self {
285 requested: AtomicBool::new(false),
286 completed: AtomicBool::new(false),
287 announcement: StopAnnouncement::new(),
288 listening_published: StdMutex::new(false),
289 listening_changed: Condvar::new(),
290 }
291 }
292
293 fn publish_listening(&self) {
294 let mut published = self
295 .listening_published
296 .lock()
297 .unwrap_or_else(|poisoned| poisoned.into_inner());
298 *published = true;
299 self.listening_changed.notify_all();
300 }
301
302 fn wait_for_listening(&self) {
303 let mut published = self
304 .listening_published
305 .lock()
306 .unwrap_or_else(|poisoned| poisoned.into_inner());
307 while !*published {
308 published = self
309 .listening_changed
310 .wait(published)
311 .unwrap_or_else(|poisoned| poisoned.into_inner());
312 }
313 }
314
315 fn complete(&self, observer: &dyn ProxyObserver, generation: u64, reason: StopReason) {
316 if !self.completed.swap(true, Ordering::AcqRel) {
317 observe(
318 observer,
319 ProxyEvent::Lifecycle {
320 generation,
321 event: LifecycleEvent::Stopped { reason },
322 },
323 );
324 }
325 }
326}
327
328struct StopAnnouncement {
329 state: StdMutex<StopAnnouncementState>,
330 emitted: Condvar,
331}
332
333impl StopAnnouncement {
334 fn new() -> Self {
335 Self {
336 state: StdMutex::new(StopAnnouncementState::Pending),
337 emitted: Condvar::new(),
338 }
339 }
340
341 fn announce(&self, observer: &dyn ProxyObserver, generation: u64) -> bool {
345 let current = std::thread::current().id();
346 let mut state = self
347 .state
348 .lock()
349 .unwrap_or_else(|poisoned| poisoned.into_inner());
350 loop {
351 match &*state {
352 StopAnnouncementState::Pending => {
353 *state = StopAnnouncementState::Emitting(current);
354 drop(state);
355 observe(
356 observer,
357 ProxyEvent::Lifecycle {
358 generation,
359 event: LifecycleEvent::Stopping,
360 },
361 );
362 let mut state = self
363 .state
364 .lock()
365 .unwrap_or_else(|poisoned| poisoned.into_inner());
366 *state = StopAnnouncementState::Emitted;
367 self.emitted.notify_all();
368 return true;
369 }
370 StopAnnouncementState::Emitting(owner) if *owner == current => return false,
371 StopAnnouncementState::Emitting(_) => {
372 state = self
373 .emitted
374 .wait(state)
375 .unwrap_or_else(|poisoned| poisoned.into_inner());
376 }
377 StopAnnouncementState::Emitted => return true,
378 }
379 }
380 }
381}
382
383fn listener_exit_handler(
384 tls_identities: Arc<LocalTlsIdentityStore>,
385 observer: Arc<dyn ProxyObserver>,
386 generation: u64,
387 stop_state: Arc<ProxyStopState>,
388) -> Arc<ListenerExitHandler> {
389 Arc::new(move |phase| match phase {
390 ListenerExitPhase::FailureDetected => {
391 stop_state.requested.store(true, Ordering::Release);
392 tls_identities.deactivate();
393 stop_state.wait_for_listening();
394 let _announced = stop_state
395 .announcement
396 .announce(observer.as_ref(), generation);
397 }
398 ListenerExitPhase::Quiesced(reason) => {
399 stop_state.requested.store(true, Ordering::Release);
400 tls_identities.deactivate();
401 stop_state.wait_for_listening();
402 let _announced = stop_state
403 .announcement
404 .announce(observer.as_ref(), generation);
405 let reason = match reason {
406 ListenerExitReason::Requested => StopReason::Requested,
407 ListenerExitReason::ListenerFailure => StopReason::ListenerFailure,
408 };
409 stop_state.complete(observer.as_ref(), generation, reason);
410 }
411 })
412}
413
414#[derive(Debug)]
415enum StopAnnouncementState {
416 Pending,
417 Emitting(ThreadId),
418 Emitted,
419}
420
421impl fmt::Debug for RunningProxy {
422 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
423 formatter
424 .debug_struct("RunningProxy")
425 .field("endpoint", &self.endpoint)
426 .field("active_clients", &self.active_clients())
427 .field(
428 "stop_requested",
429 &self.stop_state.requested.load(Ordering::Acquire),
430 )
431 .field("stop_completed", &self.is_stopped())
432 .finish()
433 }
434}
435
436impl Drop for RunningProxy {
437 fn drop(&mut self) {
438 self.stop();
439 }
440}
441
442struct ServerContext {
443 authorization: Arc<ProxyAuthorization>,
444 hns_scope: Option<crate::HostScope>,
445 routing_mode: ProxyRoutingMode,
446 limits: ProxyLimits,
447 timeouts: ProxyTimeouts,
448 backend: Arc<dyn ProxyBackend>,
449 observer: Arc<dyn ProxyObserver>,
450 metadata_observer: Arc<dyn ProxyResponseMetadataObserver>,
451 generation: u64,
452 rate_limiter: RequestRateLimiter,
453 tls_identities: Arc<LocalTlsIdentityStore>,
454}
455
456fn handle_client(
457 mut stream: TcpStream,
458 peer: SocketAddr,
459 cancellation: CancellationToken,
460 context: &ServerContext,
461) {
462 if !peer.ip().is_loopback() {
463 observe_client_rejection(context, ClientRejectionReason::InvalidPeer);
464 let _result = stream.shutdown(Shutdown::Both);
465 return;
466 }
467 if cancellation.is_cancelled() {
468 observe_client_rejection(context, ClientRejectionReason::Cancelled);
469 let _result = stream.shutdown(Shutdown::Both);
470 return;
471 }
472
473 let _result = stream.set_nodelay(true);
474 if stream
475 .set_write_timeout(Some(context.timeouts.socket_timeout()))
476 .is_err()
477 {
478 observe_client_rejection(context, ClientRejectionReason::InvalidRequest);
479 return;
480 }
481
482 let head = {
483 let mut reader =
484 DeadlineReader::new(&mut stream, context.timeouts.request_header_timeout());
485 match read_request_head(&mut reader, context.limits.max_header_bytes()) {
486 Ok(head) => head,
487 Err(error) => {
488 observe_client_rejection(context, ClientRejectionReason::InvalidRequest);
489 let (status, reason) = request_error_status(&error);
490 let _result = write_error_response(&mut stream, status, reason, &[]);
491 return;
492 }
493 }
494 };
495
496 if !context
499 .authorization
500 .verify_header_values(head.header_values(crate::PROXY_AUTHORIZATION_HEADER))
501 {
502 observe_client_rejection(context, ClientRejectionReason::AuthenticationRequired);
503 let _result = write_error_response(
504 &mut stream,
505 407,
506 "Proxy Authentication Required",
507 &[(
508 crate::PROXY_AUTHENTICATE_HEADER,
509 context.authorization.challenge_header_value(),
510 )],
511 );
512 return;
513 }
514
515 let target = match head.validated_target(None) {
516 Ok(target) => target,
517 Err(error) => {
518 observe_client_rejection(context, ClientRejectionReason::InvalidRequest);
519 let _result =
520 write_error_response(&mut stream, error.status_code(), error.reason_phrase(), &[]);
521 return;
522 }
523 };
524 let method = ObservedMethod::from_token(head.method());
525 let canonical_host = match admit_target_host(context, target.authority().host()) {
526 Ok(host) => host,
527 Err(error) => {
528 let (status_reason, rejection) = route_rejection(error);
529 if let Ok(host) = crate::NormalizedHost::parse(target.authority().host()) {
530 observe_request(
531 context,
532 &host,
533 method,
534 RequestPhase::Rejected { reason: rejection },
535 );
536 }
537 let _result = write_error_response(&mut stream, 403, status_reason, &[]);
538 return;
539 }
540 };
541 observe_request(
542 context,
543 canonical_host.as_str(),
544 method,
545 RequestPhase::Accepted,
546 );
547
548 match target {
549 RequestTarget::Absolute(absolute) => handle_direct_http(
550 &mut stream,
551 &head,
552 &absolute,
553 &canonical_host,
554 method,
555 &cancellation,
556 context,
557 ),
558 RequestTarget::Connect(authority) => handle_connect(
559 stream,
560 &head,
561 authority,
562 canonical_host,
563 method,
564 &cancellation,
565 context,
566 ),
567 RequestTarget::Origin(_) => reject_routed_request(
568 &mut stream,
569 context,
570 canonical_host.as_str(),
571 method,
572 400,
573 "Bad Request",
574 RequestRejectionReason::InvalidRequest,
575 false,
576 ),
577 }
578}
579
580#[derive(Clone, Copy, Debug, Eq, PartialEq)]
581enum RouteRejection {
582 Scope(HostScopeError),
583 SpecialUse,
584 InvalidClass,
585}
586
587fn admit_target_host(
588 context: &ServerContext,
589 raw_host: &str,
590) -> Result<crate::NormalizedHost, RouteRejection> {
591 authorize_native_gateway_host(context, raw_host)
592}
593
594fn authorize_native_gateway_host(
595 context: &ServerContext,
596 raw_host: &str,
597) -> Result<crate::NormalizedHost, RouteRejection> {
598 if context.routing_mode == ProxyRoutingMode::DaneBrowser {
599 let host = crate::NormalizedHost::parse(raw_host)
600 .map_err(|error| RouteRejection::Scope(HostScopeError::InvalidHost(error)))?;
601 if is_browser_special_use_host(host.as_str()) {
602 return Err(RouteRejection::SpecialUse);
603 }
604 return Ok(host);
608 }
609
610 context
611 .hns_scope
612 .as_ref()
613 .ok_or(RouteRejection::InvalidClass)?
614 .authorize(raw_host)
615 .map_err(RouteRejection::Scope)
616}
617
618#[derive(Clone, Copy)]
619struct HttpRoute<'a> {
620 scheme: Scheme,
621 authority: &'a Authority,
622 path_and_query: &'a str,
623}
624
625#[allow(clippy::too_many_arguments)]
626fn handle_direct_http(
627 stream: &mut TcpStream,
628 head: &RequestHead,
629 absolute: &AbsoluteTarget,
630 canonical_host: &crate::NormalizedHost,
631 method: ObservedMethod,
632 cancellation: &CancellationToken,
633 context: &ServerContext,
634) {
635 let route = HttpRoute {
636 scheme: absolute.scheme(),
637 authority: absolute.authority(),
638 path_and_query: absolute.path_and_query(),
639 };
640 let is_upgrade =
641 matches!(absolute.scheme(), Scheme::Ws | Scheme::Wss) || requests_upgrade(head);
642 let likely_main_frame =
643 !is_upgrade && is_likely_main_frame_navigation(head, route.path_and_query);
644 if !admit_rate(stream, context, canonical_host, method, likely_main_frame) {
645 return;
646 }
647
648 if is_upgrade {
649 handle_admitted_upgrade(
650 stream,
651 head,
652 route,
653 canonical_host,
654 method,
655 cancellation,
656 context,
657 );
658 } else {
659 handle_admitted_http(
660 stream,
661 head,
662 route,
663 canonical_host,
664 method,
665 cancellation,
666 context,
667 );
668 }
669}
670#[allow(clippy::too_many_arguments)]
671fn handle_connect(
672 mut stream: TcpStream,
673 head: &RequestHead,
674 authority: Authority,
675 canonical_host: crate::NormalizedHost,
676 method: ObservedMethod,
677 cancellation: &CancellationToken,
678 context: &ServerContext,
679) {
680 if requests_upgrade(head) {
681 reject_scoped_request(
682 &mut stream,
683 context,
684 &canonical_host,
685 method,
686 501,
687 "HNS Protocol Upgrade Unsupported",
688 RequestRejectionReason::InvalidRequest,
689 );
690 return;
691 }
692 if head.header_values("expect").next().is_some() {
693 reject_scoped_request(
694 &mut stream,
695 context,
696 &canonical_host,
697 method,
698 417,
699 "Expectation Failed",
700 RequestRejectionReason::InvalidRequest,
701 );
702 return;
703 }
704 let framing = match determine_body_framing(head.headers()) {
705 Ok(framing) => framing,
706 Err(error) => {
707 reject_http_request(&mut stream, context, &canonical_host, method, &error);
708 return;
709 }
710 };
711 if !matches!(framing, BodyFraming::None | BodyFraming::ContentLength(0)) {
712 reject_scoped_request(
713 &mut stream,
714 context,
715 &canonical_host,
716 method,
717 400,
718 "HNS CONNECT Body Unsupported",
719 RequestRejectionReason::InvalidRequest,
720 );
721 return;
722 }
723 if is_browser_blocked_port(authority.port()) {
724 reject_scoped_request(
725 &mut stream,
726 context,
727 &canonical_host,
728 method,
729 403,
730 "Destination Port Denied",
731 RequestRejectionReason::InvalidRequest,
732 );
733 return;
734 }
735 if cancellation.is_cancelled() {
736 observe_request(
737 context,
738 &canonical_host,
739 method,
740 RequestPhase::Rejected {
741 reason: RequestRejectionReason::Cancelled,
742 },
743 );
744 return;
745 }
746 if !admit_rate(&mut stream, context, &canonical_host, method, false) {
747 return;
748 }
749
750 let connect_started = Instant::now();
751 let connect = catch_unwind(AssertUnwindSafe(|| {
752 context.backend.open_connect(
753 ProxyRequest {
754 method: "CONNECT".to_owned(),
755 scheme: "https".to_owned(),
756 host: canonical_host.as_str().to_owned(),
757 port: authority.port(),
758 path_and_query: "/".to_owned(),
759 headers: Vec::new(),
760 body: ProxyRequestBody::Empty,
761 },
762 cancellation,
763 )
764 }))
765 .unwrap_or(Err(BackendError::Internal));
766 let connect = match connect {
767 Ok(connect) => connect,
768 Err(BackendError::Cancelled) => return,
769 Err(error) => {
770 let (status, reason) = connect_backend_error_status(error);
771 observe_request(
772 context,
773 &canonical_host,
774 method,
775 RequestPhase::BackendFailed {
776 kind: backend_failure_kind(error),
777 elapsed: connect_started.elapsed(),
778 },
779 );
780 observe_proxy_generated_response(context, &canonical_host, method, status, false);
781 let _result = write_error_response(&mut stream, status, reason, &[]);
782 return;
783 }
784 };
785 if matches!(&connect, ProxyConnectOpen::WebPkiUnavailable) {
786 reject_scoped_request(
787 &mut stream,
788 context,
789 &canonical_host,
790 method,
791 502,
792 "ICANN WebPKI Origin Unavailable",
793 RequestRejectionReason::InvalidRequest,
794 );
795 return;
796 }
797 if let ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
798 reader: origin_reader,
799 writer: origin_writer,
800 shutdown: origin_shutdown,
801 observation_id,
802 correlation_epoch,
803 publication_permit,
804 }) = connect
805 {
806 if cancellation.is_cancelled()
807 || publication_permit
808 .publish(|| {
809 if cancellation.is_cancelled() {
810 return Err(io::Error::new(
811 io::ErrorKind::Interrupted,
812 "proxy generation was cancelled before CONNECT publication",
813 ));
814 }
815 stream.write_all(CONNECT_ESTABLISHED_RESPONSE)?;
816 stream.flush()
817 })
818 .is_err()
819 {
820 origin_shutdown.shutdown();
821 let _result = stream.shutdown(Shutdown::Both);
822 return;
823 }
824 observe_connect_security_decision(
825 context,
826 &canonical_host,
827 authority.port(),
828 observation_id,
829 correlation_epoch,
830 );
831 match pump_connect_tunnel(
832 stream,
833 origin_reader,
834 origin_writer,
835 origin_shutdown,
836 cancellation,
837 ) {
838 TunnelPumpOutcome::Completed => observe_request(
839 context,
840 &canonical_host,
841 method,
842 RequestPhase::Completed {
843 status_code: 200,
844 elapsed: connect_started.elapsed(),
845 },
846 ),
847 TunnelPumpOutcome::OriginIo => observe_request(
848 context,
849 &canonical_host,
850 method,
851 RequestPhase::BackendFailed {
852 kind: BackendFailureKind::Upstream,
853 elapsed: connect_started.elapsed(),
854 },
855 ),
856 TunnelPumpOutcome::ClientIo | TunnelPumpOutcome::Cancelled => {}
857 }
858 return;
859 }
860
861 let identity = match context.tls_identities.prepare(&canonical_host) {
862 Ok(identity) => identity,
863 Err(_error) => {
864 reject_scoped_request(
865 &mut stream,
866 context,
867 &canonical_host,
868 method,
869 503,
870 "HNS Local TLS Unavailable",
871 RequestRejectionReason::InvalidRequest,
872 );
873 return;
874 }
875 };
876 debug_assert_eq!(identity.pin().host(), &canonical_host);
877 let started = Instant::now();
878 if stream.write_all(CONNECT_ESTABLISHED_RESPONSE).is_err() || stream.flush().is_err() {
879 return;
880 }
881
882 let mut tls = match accept_local_tls(
883 stream,
884 &identity,
885 &canonical_host,
886 context.timeouts.socket_timeout(),
887 cancellation,
888 ) {
889 Ok(tls) => tls,
890 Err(_error) => {
891 let reason = if cancellation.is_cancelled() {
892 RequestRejectionReason::Cancelled
893 } else {
894 RequestRejectionReason::InvalidRequest
895 };
896 observe_request(
897 context,
898 &canonical_host,
899 method,
900 RequestPhase::Rejected { reason },
901 );
902 return;
903 }
904 };
905 observe_request(
906 context,
907 &canonical_host,
908 method,
909 RequestPhase::Completed {
910 status_code: 200,
911 elapsed: started.elapsed(),
912 },
913 );
914 handle_connected_http(&mut tls, &authority, &canonical_host, cancellation, context);
915 tls.conn.send_close_notify();
916 let _result = tls.flush();
917}
918
919fn handle_connected_http(
920 stream: &mut TlsStream,
921 connected_to: &Authority,
922 outer_host: &crate::NormalizedHost,
923 cancellation: &CancellationToken,
924 context: &ServerContext,
925) {
926 let head = {
927 let mut reader = DeadlineReader::new(stream, context.timeouts.request_header_timeout());
928 match read_request_head(&mut reader, context.limits.max_header_bytes()) {
929 Ok(head) => head,
930 Err(error) => {
931 reject_http_request(stream, context, outer_host, ObservedMethod::Other, &error);
932 return;
933 }
934 }
935 };
936 let method = ObservedMethod::from_token(head.method());
937 let target = match head.validated_target(Some(connected_to)) {
938 Ok(target) => target,
939 Err(error) => {
940 reject_http_request(stream, context, outer_host, method, &error);
941 return;
942 }
943 };
944 let canonical_host = match authorize_native_gateway_host(context, target.authority().host())
945 .ok()
946 {
947 Some(host) if host == *outer_host && target.authority().port() == connected_to.port() => {
948 host
949 }
950 Some(_) | None => {
951 reject_scoped_request(
952 stream,
953 context,
954 outer_host,
955 method,
956 403,
957 "HNS Proxy Scope Denied",
958 RequestRejectionReason::HostOutsideScope,
959 );
960 return;
961 }
962 };
963 observe_request(context, &canonical_host, method, RequestPhase::Accepted);
964
965 match target {
966 RequestTarget::Origin(origin) => {
967 let route = HttpRoute {
968 scheme: Scheme::Https,
969 authority: origin.authority(),
970 path_and_query: origin.path_and_query(),
971 };
972 if requests_upgrade(&head) {
973 handle_admitted_upgrade(
974 stream,
975 &head,
976 route,
977 &canonical_host,
978 method,
979 cancellation,
980 context,
981 );
982 } else {
983 handle_admitted_http(
984 stream,
985 &head,
986 route,
987 &canonical_host,
988 method,
989 cancellation,
990 context,
991 );
992 }
993 }
994 RequestTarget::Absolute(absolute)
995 if matches!(absolute.scheme(), Scheme::Https | Scheme::Wss) =>
996 {
997 let route = HttpRoute {
998 scheme: absolute.scheme(),
999 authority: absolute.authority(),
1000 path_and_query: absolute.path_and_query(),
1001 };
1002 if absolute.scheme() == Scheme::Wss || requests_upgrade(&head) {
1003 handle_admitted_upgrade(
1004 stream,
1005 &head,
1006 route,
1007 &canonical_host,
1008 method,
1009 cancellation,
1010 context,
1011 );
1012 } else {
1013 handle_admitted_http(
1014 stream,
1015 &head,
1016 route,
1017 &canonical_host,
1018 method,
1019 cancellation,
1020 context,
1021 );
1022 }
1023 }
1024 RequestTarget::Absolute(_) | RequestTarget::Connect(_) => reject_scoped_request(
1025 stream,
1026 context,
1027 &canonical_host,
1028 method,
1029 501,
1030 "HNS Protocol Upgrade Unsupported",
1031 RequestRejectionReason::InvalidRequest,
1032 ),
1033 }
1034}
1035
1036#[allow(clippy::too_many_arguments)]
1037fn handle_admitted_upgrade<S: ClientIo + ?Sized>(
1038 stream: &mut S,
1039 head: &RequestHead,
1040 route: HttpRoute<'_>,
1041 canonical_host: &crate::NormalizedHost,
1042 method: ObservedMethod,
1043 cancellation: &CancellationToken,
1044 context: &ServerContext,
1045) {
1046 let framing = match determine_body_framing(head.headers()) {
1047 Ok(framing) => framing,
1048 Err(error) => {
1049 reject_http_request(stream, context, canonical_host, method, &error);
1050 return;
1051 }
1052 };
1053 if !matches!(framing, BodyFraming::None | BodyFraming::ContentLength(0)) {
1054 reject_scoped_request(
1055 stream,
1056 context,
1057 canonical_host,
1058 method,
1059 400,
1060 "HNS Protocol Upgrade Body Unsupported",
1061 RequestRejectionReason::InvalidRequest,
1062 );
1063 return;
1064 }
1065 if head.header_values("expect").next().is_some() {
1066 reject_scoped_request(
1067 stream,
1068 context,
1069 canonical_host,
1070 method,
1071 417,
1072 "Expectation Failed",
1073 RequestRejectionReason::InvalidRequest,
1074 );
1075 return;
1076 }
1077 let headers = match build_upgrade_forward_headers(head, route, canonical_host) {
1078 Ok(headers) => headers,
1079 Err(error) => {
1080 reject_http_request(stream, context, canonical_host, method, &error);
1081 return;
1082 }
1083 };
1084 if stream.clear_client_read_deadline().is_err() {
1085 observe_request(
1086 context,
1087 canonical_host,
1088 method,
1089 RequestPhase::Rejected {
1090 reason: RequestRejectionReason::InvalidRequest,
1091 },
1092 );
1093 return;
1094 }
1095 if cancellation.is_cancelled() {
1096 observe_request(
1097 context,
1098 canonical_host,
1099 method,
1100 RequestPhase::Rejected {
1101 reason: RequestRejectionReason::Cancelled,
1102 },
1103 );
1104 return;
1105 }
1106
1107 execute_backend_tunnel(
1108 stream,
1109 context,
1110 canonical_host,
1111 method,
1112 ProxyRequest {
1113 method: head.method().to_owned(),
1114 scheme: route.scheme.as_str().to_owned(),
1115 host: canonical_host.as_str().to_owned(),
1116 port: route.authority.port(),
1117 path_and_query: route.path_and_query.to_owned(),
1118 headers,
1119 body: ProxyRequestBody::Empty,
1120 },
1121 cancellation,
1122 );
1123}
1124
1125fn admit_rate<W: Write + ?Sized>(
1126 stream: &mut W,
1127 context: &ServerContext,
1128 canonical_host: &str,
1129 method: ObservedMethod,
1130 likely_main_frame: bool,
1131) -> bool {
1132 match context.rate_limiter.check(canonical_host, Instant::now()) {
1133 RateLimitDecision::Allowed => true,
1134 RateLimitDecision::Limited { scope, retry_after } => {
1135 let reason = match scope {
1136 RateLimitScope::Global => RequestRejectionReason::GlobalRateLimit,
1137 RateLimitScope::Host => RequestRejectionReason::HostRateLimit,
1138 };
1139 observe_request(
1140 context,
1141 canonical_host,
1142 method,
1143 RequestPhase::Rejected { reason },
1144 );
1145 observe_proxy_generated_response(
1146 context,
1147 canonical_host,
1148 method,
1149 429,
1150 likely_main_frame,
1151 );
1152 let _result = write_error_response(
1153 stream,
1154 429,
1155 "Too Many Requests",
1156 &[("Retry-After", retry_after_seconds(retry_after).to_string())],
1157 );
1158 false
1159 }
1160 }
1161}
1162
1163#[allow(clippy::too_many_arguments)]
1164fn handle_admitted_http<S: ClientIo + ?Sized>(
1165 stream: &mut S,
1166 head: &RequestHead,
1167 route: HttpRoute<'_>,
1168 canonical_host: &crate::NormalizedHost,
1169 method: ObservedMethod,
1170 cancellation: &CancellationToken,
1171 context: &ServerContext,
1172) {
1173 let likely_main_frame = is_likely_main_frame_navigation(head, route.path_and_query);
1174 let framing = match determine_body_framing(head.headers()) {
1175 Ok(framing) => framing,
1176 Err(error) => {
1177 reject_http_request_with_status(
1178 stream,
1179 context,
1180 canonical_host,
1181 method,
1182 &error,
1183 likely_main_frame,
1184 );
1185 return;
1186 }
1187 };
1188 if matches!(
1189 framing,
1190 BodyFraming::ContentLength(length) if length > context.limits.max_body_bytes()
1191 ) {
1192 reject_http_request_with_status(
1193 stream,
1194 context,
1195 canonical_host,
1196 method,
1197 &Http1Error::BodyTooLarge,
1198 likely_main_frame,
1199 );
1200 return;
1201 }
1202 let expects_continue = match expects_continue(head) {
1203 Ok(expects) => expects,
1204 Err(()) => {
1205 reject_scoped_request_with_status(
1206 stream,
1207 context,
1208 canonical_host,
1209 method,
1210 417,
1211 "Expectation Failed",
1212 RequestRejectionReason::InvalidRequest,
1213 likely_main_frame,
1214 );
1215 return;
1216 }
1217 };
1218 let forward_headers = match build_forward_headers(head, route, canonical_host) {
1219 Ok(headers) => headers,
1220 Err(error) => {
1221 reject_http_request_with_status(
1222 stream,
1223 context,
1224 canonical_host,
1225 method,
1226 &error,
1227 likely_main_frame,
1228 );
1229 return;
1230 }
1231 };
1232
1233 if expects_continue
1234 && !matches!(framing, BodyFraming::None)
1235 && (stream.write_all(b"HTTP/1.1 100 Continue\r\n\r\n").is_err() || stream.flush().is_err())
1236 {
1237 return;
1238 }
1239 let body = {
1240 let mut reader = DeadlineReader::new(stream, context.timeouts.socket_timeout());
1241 match read_request_body(&mut reader, framing, context.limits.max_body_bytes()) {
1242 Ok(bytes) => bytes,
1243 Err(error) => {
1244 reject_http_request_with_status(
1245 stream,
1246 context,
1247 canonical_host,
1248 method,
1249 &error,
1250 likely_main_frame,
1251 );
1252 return;
1253 }
1254 }
1255 };
1256 if stream.clear_client_read_deadline().is_err() {
1257 observe_request(
1258 context,
1259 canonical_host,
1260 method,
1261 RequestPhase::Rejected {
1262 reason: RequestRejectionReason::InvalidRequest,
1263 },
1264 );
1265 return;
1266 }
1267 if cancellation.is_cancelled() {
1268 observe_request(
1269 context,
1270 canonical_host,
1271 method,
1272 RequestPhase::Rejected {
1273 reason: RequestRejectionReason::Cancelled,
1274 },
1275 );
1276 return;
1277 }
1278
1279 let request = ProxyRequest {
1280 method: head.method().to_owned(),
1281 scheme: route.scheme.as_str().to_owned(),
1282 host: canonical_host.as_str().to_owned(),
1283 port: route.authority.port(),
1284 path_and_query: route.path_and_query.to_owned(),
1285 headers: forward_headers,
1286 body: if body.is_empty() {
1287 ProxyRequestBody::Empty
1288 } else {
1289 ProxyRequestBody::Bytes(body)
1290 },
1291 };
1292 execute_backend(
1293 stream,
1294 context,
1295 canonical_host,
1296 method,
1297 likely_main_frame,
1298 head.method(),
1299 request,
1300 cancellation,
1301 );
1302}
1303
1304#[allow(clippy::too_many_arguments)]
1305fn execute_backend<W: Write + ?Sized>(
1306 stream: &mut W,
1307 context: &ServerContext,
1308 host: &crate::NormalizedHost,
1309 method: ObservedMethod,
1310 likely_main_frame: bool,
1311 request_method: &str,
1312 request: ProxyRequest,
1313 cancellation: &CancellationToken,
1314) {
1315 let started = Instant::now();
1316 let response = catch_unwind(AssertUnwindSafe(|| {
1317 context.backend.execute(request, cancellation)
1318 }))
1319 .unwrap_or(Err(BackendError::Internal));
1320 let response = match response {
1321 Ok(response) => response,
1322 Err(error) => {
1323 observe_request(
1324 context,
1325 host,
1326 method,
1327 RequestPhase::BackendFailed {
1328 kind: backend_failure_kind(error),
1329 elapsed: started.elapsed(),
1330 },
1331 );
1332 if error != BackendError::Cancelled && !cancellation.is_cancelled() {
1333 let (status, reason) = backend_error_status(error);
1334 observe_proxy_generated_response(context, host, method, status, likely_main_frame);
1335 let _result = write_error_response(stream, status, reason, &[]);
1336 }
1337 return;
1338 }
1339 };
1340 if cancellation.is_cancelled() {
1341 return;
1342 }
1343 let status = response.head.status_code;
1344 let observation_id = response.head.observation_id;
1345 match write_backend_response(stream, request_method, response, cancellation, |metadata| {
1346 observe_response_metadata(
1347 context,
1348 host,
1349 method,
1350 status,
1351 likely_main_frame,
1352 observation_id,
1353 metadata,
1354 );
1355 }) {
1356 Ok(()) => observe_request(
1357 context,
1358 host,
1359 method,
1360 RequestPhase::Completed {
1361 status_code: status,
1362 elapsed: started.elapsed(),
1363 },
1364 ),
1365 Err(WriteBackendError::InvalidBeforeHead) => {
1366 observe_invalid_response(context, host, method, started.elapsed());
1367 if !cancellation.is_cancelled() {
1368 observe_proxy_generated_response(context, host, method, 502, likely_main_frame);
1369 let _result = write_error_response(stream, 502, "Invalid Upstream Response", &[]);
1370 }
1371 }
1372 Err(WriteBackendError::InvalidAfterHead) => {
1373 observe_invalid_response(context, host, method, started.elapsed());
1374 }
1375 Err(WriteBackendError::Io) => {}
1376 }
1377}
1378
1379#[allow(clippy::too_many_arguments)]
1380fn execute_backend_tunnel<S: ClientIo + ?Sized>(
1381 client: &mut S,
1382 context: &ServerContext,
1383 host: &crate::NormalizedHost,
1384 method: ObservedMethod,
1385 request: ProxyRequest,
1386 cancellation: &CancellationToken,
1387) {
1388 let started = Instant::now();
1389 let request_method = request.method.clone();
1390 let tunnel = catch_unwind(AssertUnwindSafe(|| {
1391 context.backend.open_tunnel(request, cancellation)
1392 }))
1393 .unwrap_or(Err(BackendError::Internal));
1394 let opened = match tunnel {
1395 Ok(opened) => opened,
1396 Err(error) => {
1397 observe_request(
1398 context,
1399 host,
1400 method,
1401 RequestPhase::BackendFailed {
1402 kind: backend_failure_kind(error),
1403 elapsed: started.elapsed(),
1404 },
1405 );
1406 if error != BackendError::Cancelled && !cancellation.is_cancelled() {
1407 let (status, reason) = backend_error_status(error);
1408 let _result = write_error_response(client, status, reason, &[]);
1409 }
1410 return;
1411 }
1412 };
1413 if cancellation.is_cancelled() {
1414 return;
1415 }
1416 let ProxyTunnel {
1417 head,
1418 stream: mut origin,
1419 publication_permit,
1420 } = match opened {
1421 ProxyTunnelOpen::Tunnel(tunnel) => tunnel,
1422 ProxyTunnelOpen::Response(response) => {
1423 let status = response.head.status_code;
1424 let observation_id = response.head.observation_id;
1425 match write_backend_response(
1426 client,
1427 &request_method,
1428 response,
1429 cancellation,
1430 |metadata| {
1431 observe_response_metadata(
1432 context,
1433 host,
1434 method,
1435 status,
1436 false,
1437 observation_id,
1438 metadata,
1439 );
1440 },
1441 ) {
1442 Ok(()) => observe_request(
1443 context,
1444 host,
1445 method,
1446 RequestPhase::Completed {
1447 status_code: status,
1448 elapsed: started.elapsed(),
1449 },
1450 ),
1451 Err(WriteBackendError::InvalidBeforeHead) => {
1452 observe_invalid_response(context, host, method, started.elapsed());
1453 if !cancellation.is_cancelled() {
1454 let _result =
1455 write_error_response(client, 502, "Invalid Upstream Response", &[]);
1456 }
1457 }
1458 Err(WriteBackendError::InvalidAfterHead) => {
1459 observe_invalid_response(context, host, method, started.elapsed());
1460 }
1461 Err(WriteBackendError::Io) => {}
1462 }
1463 return;
1464 }
1465 };
1466 let header_pairs: Vec<_> = head
1467 .headers
1468 .into_iter()
1469 .map(|header| (header.name, header.value))
1470 .collect();
1471 let sanitized = match sanitize_response_headers(&header_pairs) {
1472 Ok(sanitized) => sanitized,
1473 Err(_error) => {
1474 observe_invalid_response(context, host, method, started.elapsed());
1475 if !cancellation.is_cancelled() {
1476 let _result = write_error_response(client, 502, "Invalid Upstream Response", &[]);
1477 }
1478 return;
1479 }
1480 };
1481 let encoded =
1482 match encode_upgrade_response_head(head.status_code, &head.reason_phrase, &header_pairs) {
1483 Ok(encoded) => encoded,
1484 Err(_error) => {
1485 observe_invalid_response(context, host, method, started.elapsed());
1486 if !cancellation.is_cancelled() {
1487 let _result =
1488 write_error_response(client, 502, "Invalid Upstream Response", &[]);
1489 }
1490 return;
1491 }
1492 };
1493 if cancellation.is_cancelled()
1494 || publication_permit
1495 .publish(|| {
1496 if cancellation.is_cancelled() {
1497 return Err(io::Error::new(
1498 io::ErrorKind::Interrupted,
1499 "proxy generation was cancelled before publication",
1500 ));
1501 }
1502 client.write_all(encoded.as_bytes())?;
1503 client.flush()
1504 })
1505 .is_err()
1506 {
1507 return;
1508 }
1509 observe_response_metadata(
1510 context,
1511 host,
1512 method,
1513 101,
1514 false,
1515 head.observation_id,
1516 sanitized.metadata(),
1517 );
1518
1519 match pump_tunnel(client, origin.as_mut(), cancellation) {
1520 TunnelPumpOutcome::Completed => observe_request(
1521 context,
1522 host,
1523 method,
1524 RequestPhase::Completed {
1525 status_code: 101,
1526 elapsed: started.elapsed(),
1527 },
1528 ),
1529 TunnelPumpOutcome::OriginIo => observe_request(
1530 context,
1531 host,
1532 method,
1533 RequestPhase::BackendFailed {
1534 kind: BackendFailureKind::Upstream,
1535 elapsed: started.elapsed(),
1536 },
1537 ),
1538 TunnelPumpOutcome::ClientIo | TunnelPumpOutcome::Cancelled => {}
1539 }
1540}
1541
1542#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1543enum TunnelPumpOutcome {
1544 Completed,
1545 ClientIo,
1546 OriginIo,
1547 Cancelled,
1548}
1549
1550fn pump_tunnel<S: ClientIo + ?Sized>(
1551 client: &mut S,
1552 origin: &mut dyn crate::ProxyTunnelIo,
1553 cancellation: &CancellationToken,
1554) -> TunnelPumpOutcome {
1555 let mut client_buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1556 let mut origin_buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1557 loop {
1558 if cancellation.is_cancelled() {
1559 return TunnelPumpOutcome::Cancelled;
1560 }
1561
1562 let now = Instant::now();
1563 let deadline = now.checked_add(TUNNEL_CLIENT_POLL_INTERVAL).unwrap_or(now);
1564 if client.set_client_read_deadline(deadline).is_err() {
1565 return TunnelPumpOutcome::ClientIo;
1566 }
1567 let client_read = client.read(&mut client_buffer);
1568 if client.clear_client_read_deadline().is_err() {
1569 return TunnelPumpOutcome::ClientIo;
1570 }
1571 match client_read {
1572 Ok(0) => return TunnelPumpOutcome::Completed,
1573 Ok(count) if count > client_buffer.len() => return TunnelPumpOutcome::ClientIo,
1574 Ok(count) => {
1575 let write = catch_unwind(AssertUnwindSafe(|| {
1576 origin.write_all(&client_buffer[..count])?;
1577 origin.flush()
1578 }));
1579 match write {
1580 Ok(Ok(())) => {}
1581 Ok(Err(_)) | Err(_) => return TunnelPumpOutcome::OriginIo,
1582 }
1583 }
1584 Err(error) if retryable_tunnel_read(&error) => {}
1585 Err(_error) => return TunnelPumpOutcome::ClientIo,
1586 }
1587
1588 if cancellation.is_cancelled() {
1589 return TunnelPumpOutcome::Cancelled;
1590 }
1591 let origin_read = catch_unwind(AssertUnwindSafe(|| origin.read(&mut origin_buffer)));
1592 match origin_read {
1593 Ok(Ok(0)) => return TunnelPumpOutcome::Completed,
1594 Ok(Ok(count)) if count > origin_buffer.len() => return TunnelPumpOutcome::OriginIo,
1595 Ok(Ok(count)) => {
1596 if client.write_all(&origin_buffer[..count]).is_err() || client.flush().is_err() {
1597 return TunnelPumpOutcome::ClientIo;
1598 }
1599 }
1600 Ok(Err(error)) if retryable_tunnel_read(&error) => {}
1601 Ok(Err(_)) | Err(_) => return TunnelPumpOutcome::OriginIo,
1602 }
1603 }
1604}
1605
1606fn retryable_tunnel_read(error: &io::Error) -> bool {
1607 matches!(
1608 error.kind(),
1609 io::ErrorKind::Interrupted | io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
1610 )
1611}
1612
1613fn pump_connect_tunnel(
1614 client: TcpStream,
1615 origin_reader: Box<dyn Read + Send>,
1616 origin_writer: Box<dyn Write + Send>,
1617 origin_shutdown: Arc<dyn crate::ProxyConnectShutdown>,
1618 cancellation: &CancellationToken,
1619) -> TunnelPumpOutcome {
1620 let client_reader = match client.try_clone() {
1621 Ok(stream) => stream,
1622 Err(_) => {
1623 origin_shutdown.shutdown();
1624 let _result = client.shutdown(Shutdown::Both);
1625 return TunnelPumpOutcome::ClientIo;
1626 }
1627 };
1628 let client_shutdown = match client.try_clone() {
1629 Ok(stream) => stream,
1630 Err(_) => {
1631 origin_shutdown.shutdown();
1632 let _result = client.shutdown(Shutdown::Both);
1633 return TunnelPumpOutcome::ClientIo;
1634 }
1635 };
1636 if client_reader
1637 .set_read_timeout(Some(CONNECT_TUNNEL_IO_TIMEOUT))
1638 .is_err()
1639 || client
1640 .set_write_timeout(Some(CONNECT_TUNNEL_IO_TIMEOUT))
1641 .is_err()
1642 {
1643 origin_shutdown.shutdown();
1644 let _result = client.shutdown(Shutdown::Both);
1645 return TunnelPumpOutcome::ClientIo;
1646 }
1647
1648 let first_outcome = Arc::new(AtomicU8::new(0));
1652 let upload_outcome = Arc::clone(&first_outcome);
1653 let upload_cancellation = cancellation.clone();
1654 let upload_origin_shutdown = Arc::clone(&origin_shutdown);
1655 let upload_client_shutdown = match client_shutdown.try_clone() {
1656 Ok(stream) => stream,
1657 Err(_) => {
1658 origin_shutdown.shutdown();
1659 let _result = client.shutdown(Shutdown::Both);
1660 return TunnelPumpOutcome::ClientIo;
1661 }
1662 };
1663 let upload = match thread::Builder::new()
1664 .name("hns-proxy-connect-upload".to_owned())
1665 .spawn(move || {
1666 pump_connect_upload(
1667 client_reader,
1668 origin_writer,
1669 &upload_cancellation,
1670 &upload_outcome,
1671 upload_client_shutdown,
1672 upload_origin_shutdown,
1673 );
1674 }) {
1675 Ok(upload) => upload,
1676 Err(_) => {
1677 origin_shutdown.shutdown();
1678 let _result = client.shutdown(Shutdown::Both);
1679 return TunnelPumpOutcome::OriginIo;
1680 }
1681 };
1682
1683 pump_connect_download(
1684 client,
1685 origin_reader,
1686 cancellation,
1687 &first_outcome,
1688 client_shutdown,
1689 Arc::clone(&origin_shutdown),
1690 );
1691 if upload.join().is_err() {
1692 record_connect_outcome(&first_outcome, TunnelPumpOutcome::OriginIo);
1693 origin_shutdown.shutdown();
1694 }
1695 connect_outcome(&first_outcome)
1696}
1697
1698fn pump_connect_upload(
1699 mut client: TcpStream,
1700 mut origin: Box<dyn Write + Send>,
1701 cancellation: &CancellationToken,
1702 first_outcome: &AtomicU8,
1703 client_shutdown: TcpStream,
1704 origin_shutdown: Arc<dyn crate::ProxyConnectShutdown>,
1705) {
1706 let mut buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1707 let outcome = loop {
1708 if cancellation.is_cancelled() {
1709 break TunnelPumpOutcome::Cancelled;
1710 }
1711 if first_outcome.load(Ordering::Acquire) != 0 {
1712 return;
1713 }
1714 match client.read(&mut buffer) {
1715 Ok(0) => break TunnelPumpOutcome::Completed,
1716 Ok(count) if count > buffer.len() => break TunnelPumpOutcome::ClientIo,
1717 Ok(count) => {
1718 let write = catch_unwind(AssertUnwindSafe(|| {
1719 origin.write_all(&buffer[..count])?;
1720 origin.flush()
1721 }));
1722 if !matches!(write, Ok(Ok(()))) {
1723 break if cancellation.is_cancelled() {
1724 TunnelPumpOutcome::Cancelled
1725 } else {
1726 TunnelPumpOutcome::OriginIo
1727 };
1728 }
1729 }
1730 Err(error) if retryable_tunnel_read(&error) => {}
1731 Err(_) => {
1732 break if cancellation.is_cancelled() {
1733 TunnelPumpOutcome::Cancelled
1734 } else {
1735 TunnelPumpOutcome::ClientIo
1736 };
1737 }
1738 }
1739 };
1740 record_connect_outcome(first_outcome, outcome);
1741 origin_shutdown.shutdown();
1742 let _result = client_shutdown.shutdown(Shutdown::Both);
1743}
1744
1745fn pump_connect_download(
1746 mut client: TcpStream,
1747 mut origin: Box<dyn Read + Send>,
1748 cancellation: &CancellationToken,
1749 first_outcome: &AtomicU8,
1750 client_shutdown: TcpStream,
1751 origin_shutdown: Arc<dyn crate::ProxyConnectShutdown>,
1752) {
1753 let mut buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1754 let outcome = loop {
1755 if cancellation.is_cancelled() {
1756 break TunnelPumpOutcome::Cancelled;
1757 }
1758 if first_outcome.load(Ordering::Acquire) != 0 {
1759 return;
1760 }
1761 let read = catch_unwind(AssertUnwindSafe(|| origin.read(&mut buffer)));
1762 match read {
1763 Ok(Ok(0)) => break TunnelPumpOutcome::Completed,
1764 Ok(Ok(count)) if count > buffer.len() => break TunnelPumpOutcome::OriginIo,
1765 Ok(Ok(count)) => {
1766 if client.write_all(&buffer[..count]).is_err() || client.flush().is_err() {
1767 break if cancellation.is_cancelled() {
1768 TunnelPumpOutcome::Cancelled
1769 } else {
1770 TunnelPumpOutcome::ClientIo
1771 };
1772 }
1773 }
1774 Ok(Err(error)) if retryable_tunnel_read(&error) => {}
1775 Ok(Err(_)) | Err(_) => {
1776 break if cancellation.is_cancelled() {
1777 TunnelPumpOutcome::Cancelled
1778 } else {
1779 TunnelPumpOutcome::OriginIo
1780 };
1781 }
1782 }
1783 };
1784 record_connect_outcome(first_outcome, outcome);
1785 origin_shutdown.shutdown();
1786 let _result = client_shutdown.shutdown(Shutdown::Both);
1787}
1788
1789fn record_connect_outcome(first_outcome: &AtomicU8, outcome: TunnelPumpOutcome) {
1790 let _result = first_outcome.compare_exchange(
1791 0,
1792 connect_outcome_code(outcome),
1793 Ordering::AcqRel,
1794 Ordering::Acquire,
1795 );
1796}
1797
1798fn connect_outcome(first_outcome: &AtomicU8) -> TunnelPumpOutcome {
1799 match first_outcome.load(Ordering::Acquire) {
1800 1 => TunnelPumpOutcome::Completed,
1801 2 => TunnelPumpOutcome::ClientIo,
1802 3 => TunnelPumpOutcome::OriginIo,
1803 4 => TunnelPumpOutcome::Cancelled,
1804 _ => TunnelPumpOutcome::OriginIo,
1805 }
1806}
1807
1808const fn connect_outcome_code(outcome: TunnelPumpOutcome) -> u8 {
1809 match outcome {
1810 TunnelPumpOutcome::Completed => 1,
1811 TunnelPumpOutcome::ClientIo => 2,
1812 TunnelPumpOutcome::OriginIo => 3,
1813 TunnelPumpOutcome::Cancelled => 4,
1814 }
1815}
1816
1817fn observe_invalid_response(
1818 context: &ServerContext,
1819 host: &crate::NormalizedHost,
1820 method: ObservedMethod,
1821 elapsed: Duration,
1822) {
1823 observe_request(
1824 context,
1825 host,
1826 method,
1827 RequestPhase::BackendFailed {
1828 kind: BackendFailureKind::InvalidResponse,
1829 elapsed,
1830 },
1831 );
1832}
1833
1834fn write_backend_response<W, F>(
1835 stream: &mut W,
1836 request_method: &str,
1837 response: ProxyResponse,
1838 cancellation: &CancellationToken,
1839 observe_metadata: F,
1840) -> Result<(), WriteBackendError>
1841where
1842 W: Write + ?Sized,
1843 F: FnOnce(&crate::InternalResponseMetadata),
1844{
1845 let ProxyResponse {
1846 head,
1847 body,
1848 publication_permit,
1849 } = response;
1850 let header_pairs: Vec<_> = head
1851 .headers
1852 .into_iter()
1853 .map(|header| (header.name, header.value))
1854 .collect();
1855 let headers = sanitize_response_headers(&header_pairs)
1856 .map_err(|_error| WriteBackendError::InvalidBeforeHead)?;
1857 let body_len = body.expected_len();
1858 let encoded = encode_response_head(
1859 request_method,
1860 head.status_code,
1861 &head.reason_phrase,
1862 &headers,
1863 body_len,
1864 )
1865 .map_err(|_error| WriteBackendError::InvalidBeforeHead)?;
1866 let body_allowed = encoded.body_allowed();
1867 if cancellation.is_cancelled() {
1868 return Err(WriteBackendError::Io);
1869 }
1870 publication_permit
1871 .publish(|| {
1872 if cancellation.is_cancelled() {
1873 return Err(io::Error::new(
1874 io::ErrorKind::Interrupted,
1875 "proxy generation was cancelled before publication",
1876 ));
1877 }
1878 stream.write_all(encoded.as_bytes())?;
1879 stream.flush()
1880 })
1881 .map_err(|_error| WriteBackendError::Io)?;
1882 observe_metadata(headers.metadata());
1883 if !body_allowed {
1884 return Ok(());
1885 }
1886 write_response_body(stream, body, cancellation)
1887}
1888
1889fn write_response_body<W: Write + ?Sized>(
1890 stream: &mut W,
1891 body: ProxyResponseBody,
1892 cancellation: &CancellationToken,
1893) -> Result<(), WriteBackendError> {
1894 match body {
1895 ProxyResponseBody::Bytes(bytes) => {
1896 if cancellation.is_cancelled() {
1897 return Err(WriteBackendError::Io);
1898 }
1899 stream
1900 .write_all(&bytes)
1901 .map_err(|_error| WriteBackendError::Io)
1902 }
1903 ProxyResponseBody::Stream {
1904 expected_len,
1905 mut reader,
1906 } => copy_exact_response(stream, reader.as_mut(), expected_len, cancellation),
1907 }
1908}
1909
1910fn copy_exact_response<W: Write + ?Sized>(
1911 stream: &mut W,
1912 reader: &mut dyn Read,
1913 mut remaining: u64,
1914 cancellation: &CancellationToken,
1915) -> Result<(), WriteBackendError> {
1916 let mut buffer = [0_u8; RESPONSE_COPY_BUFFER_BYTES];
1917 while remaining != 0 {
1918 if cancellation.is_cancelled() {
1919 return Err(WriteBackendError::Io);
1920 }
1921 let limit = usize::try_from(remaining.min(buffer.len() as u64))
1922 .map_err(|_error| WriteBackendError::InvalidAfterHead)?;
1923 let count = catch_unwind(AssertUnwindSafe(|| reader.read(&mut buffer[..limit])))
1924 .map_err(|_panic| WriteBackendError::InvalidAfterHead)?
1925 .map_err(|_error| WriteBackendError::InvalidAfterHead)?;
1926 if count == 0 || count > limit {
1927 return Err(WriteBackendError::InvalidAfterHead);
1928 }
1929 stream
1930 .write_all(&buffer[..count])
1931 .map_err(|_error| WriteBackendError::Io)?;
1932 remaining = remaining
1933 .checked_sub(count as u64)
1934 .ok_or(WriteBackendError::InvalidAfterHead)?;
1935 }
1936 Ok(())
1937}
1938
1939#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1940enum WriteBackendError {
1941 InvalidBeforeHead,
1942 InvalidAfterHead,
1943 Io,
1944}
1945
1946fn build_forward_headers(
1947 head: &RequestHead,
1948 route: HttpRoute<'_>,
1949 host: &crate::NormalizedHost,
1950) -> Result<Vec<ProxyHeader>, Http1Error> {
1951 let mut headers: Vec<_> = sanitize_forward_headers(head.headers())?
1952 .into_iter()
1953 .filter(|header| {
1954 !header.name().eq_ignore_ascii_case("host")
1955 && !header.name().eq_ignore_ascii_case("content-length")
1956 && !header.name().eq_ignore_ascii_case("expect")
1957 })
1958 .map(|header| ProxyHeader::new(header.name(), header.value()))
1959 .collect();
1960 let default_port = route.scheme.default_port();
1961 let host_value = if route.authority.port() == default_port {
1962 host.as_str().to_owned()
1963 } else {
1964 format!("{}:{}", host.as_str(), route.authority.port())
1965 };
1966 headers.push(ProxyHeader::new("Host", host_value));
1967 Ok(headers)
1968}
1969
1970fn build_upgrade_forward_headers(
1971 head: &RequestHead,
1972 route: HttpRoute<'_>,
1973 host: &crate::NormalizedHost,
1974) -> Result<Vec<ProxyHeader>, Http1Error> {
1975 let mut headers: Vec<_> = sanitize_upgrade_forward_headers(head.headers())?
1976 .into_iter()
1977 .filter(|header| {
1978 !header.name().eq_ignore_ascii_case("host")
1979 && !header.name().eq_ignore_ascii_case("content-length")
1980 && !header.name().eq_ignore_ascii_case("expect")
1981 })
1982 .map(|header| ProxyHeader::new(header.name(), header.value()))
1983 .collect();
1984 let default_port = route.scheme.default_port();
1985 let host_value = if route.authority.port() == default_port {
1986 host.as_str().to_owned()
1987 } else {
1988 format!("{}:{}", host.as_str(), route.authority.port())
1989 };
1990 headers.push(ProxyHeader::new("Host", host_value));
1991 Ok(headers)
1992}
1993
1994fn expects_continue(head: &RequestHead) -> Result<bool, ()> {
1995 let mut values = head.header_values("expect");
1996 let Some(first) = values.next() else {
1997 return Ok(false);
1998 };
1999 if values.next().is_some() || !first.eq_ignore_ascii_case("100-continue") {
2000 return Err(());
2001 }
2002 Ok(true)
2003}
2004
2005fn is_likely_main_frame_navigation(head: &RequestHead, path_and_query: &str) -> bool {
2008 if !head.method().eq_ignore_ascii_case("GET") && !head.method().eq_ignore_ascii_case("HEAD") {
2009 return false;
2010 }
2011
2012 if let Some(fetch_destination) = head.header_values("sec-fetch-dest").next() {
2016 return fetch_destination.eq_ignore_ascii_case("document");
2017 }
2018 if head
2019 .header_values("sec-fetch-mode")
2020 .next()
2021 .is_some_and(|mode| mode.eq_ignore_ascii_case("navigate"))
2022 {
2023 return true;
2024 }
2025 if head.header_values("upgrade-insecure-requests").next() == Some("1") {
2026 return true;
2027 }
2028 if head
2029 .header_values("accept")
2030 .next()
2031 .is_some_and(|accept| accept.to_ascii_lowercase().contains("text/html"))
2032 {
2033 return true;
2034 }
2035 path_and_query == "/"
2036}
2037
2038fn requests_upgrade(head: &RequestHead) -> bool {
2039 head.header_values("upgrade").next().is_some()
2040 || head
2041 .header_values("connection")
2042 .flat_map(|value| value.split(','))
2043 .any(|token| token.trim().eq_ignore_ascii_case("upgrade"))
2044}
2045
2046fn reject_http_request<W: Write + ?Sized>(
2047 stream: &mut W,
2048 context: &ServerContext,
2049 host: &crate::NormalizedHost,
2050 method: ObservedMethod,
2051 error: &Http1Error,
2052) {
2053 reject_http_request_with_status(stream, context, host, method, error, false);
2054}
2055
2056fn reject_http_request_with_status<W: Write + ?Sized>(
2057 stream: &mut W,
2058 context: &ServerContext,
2059 host: &crate::NormalizedHost,
2060 method: ObservedMethod,
2061 error: &Http1Error,
2062 likely_main_frame: bool,
2063) {
2064 let rejection = if matches!(error, Http1Error::BodyTooLarge) {
2065 RequestRejectionReason::RequestTooLarge
2066 } else {
2067 RequestRejectionReason::InvalidRequest
2068 };
2069 let (status, status_reason) = request_error_status(error);
2070 reject_scoped_request_with_status(
2071 stream,
2072 context,
2073 host,
2074 method,
2075 status,
2076 status_reason,
2077 rejection,
2078 likely_main_frame,
2079 );
2080}
2081
2082#[allow(clippy::too_many_arguments)]
2083fn reject_routed_request<W: Write + ?Sized>(
2084 stream: &mut W,
2085 context: &ServerContext,
2086 host: &str,
2087 method: ObservedMethod,
2088 status: u16,
2089 reason: &'static str,
2090 rejection: RequestRejectionReason,
2091 likely_main_frame: bool,
2092) {
2093 observe_request(
2094 context,
2095 host,
2096 method,
2097 RequestPhase::Rejected { reason: rejection },
2098 );
2099 observe_proxy_generated_response(context, host, method, status, likely_main_frame);
2100 let _result = write_error_response(stream, status, reason, &[]);
2101}
2102
2103#[allow(clippy::too_many_arguments)]
2104fn reject_scoped_request<W: Write + ?Sized>(
2105 stream: &mut W,
2106 context: &ServerContext,
2107 host: &crate::NormalizedHost,
2108 method: ObservedMethod,
2109 status: u16,
2110 reason: &'static str,
2111 rejection: RequestRejectionReason,
2112) {
2113 reject_scoped_request_with_status(
2114 stream, context, host, method, status, reason, rejection, false,
2115 );
2116}
2117
2118#[allow(clippy::too_many_arguments)]
2119fn reject_scoped_request_with_status<W: Write + ?Sized>(
2120 stream: &mut W,
2121 context: &ServerContext,
2122 host: &crate::NormalizedHost,
2123 method: ObservedMethod,
2124 status: u16,
2125 reason: &'static str,
2126 rejection: RequestRejectionReason,
2127 likely_main_frame: bool,
2128) {
2129 observe_request(
2130 context,
2131 host,
2132 method,
2133 RequestPhase::Rejected { reason: rejection },
2134 );
2135 observe_proxy_generated_response(context, host, method, status, likely_main_frame);
2136 let _result = write_error_response(stream, status, reason, &[]);
2137}
2138
2139fn route_rejection(error: RouteRejection) -> (&'static str, RequestRejectionReason) {
2140 match error {
2141 RouteRejection::Scope(HostScopeError::OutOfScope) => (
2142 "HNS Proxy Scope Denied",
2143 RequestRejectionReason::HostOutsideScope,
2144 ),
2145 RouteRejection::Scope(HostScopeError::NotHns | HostScopeError::InvalidHost(_))
2146 | RouteRejection::SpecialUse
2147 | RouteRejection::InvalidClass => (
2148 "Proxy Scope Denied",
2149 RequestRejectionReason::HostOutsideScope,
2150 ),
2151 }
2152}
2153
2154fn request_error_status(error: &Http1Error) -> (u16, &'static str) {
2155 match error {
2156 Http1Error::Io(source)
2157 if matches!(
2158 source.kind(),
2159 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
2160 ) =>
2161 {
2162 (408, "Request Timeout")
2163 }
2164 _ => (error.status_code(), error.reason_phrase()),
2165 }
2166}
2167
2168fn backend_error_status(error: BackendError) -> (u16, &'static str) {
2169 match error {
2170 BackendError::Cancelled => (503, "Proxy Request Cancelled"),
2171 BackendError::InvalidRequest => (400, "Invalid Gateway Request"),
2172 BackendError::PolicyDenied => (403, "Gateway Policy Denied"),
2173 BackendError::ResolutionFailed => (502, "HNS Resolution Failed"),
2174 BackendError::TlsValidationFailed => (502, "HNS TLS Validation Failed"),
2175 BackendError::UpstreamUnavailable => (502, "HNS Upstream Unavailable"),
2176 BackendError::InvalidResponse => (502, "Invalid Upstream Response"),
2177 BackendError::ResponseTooLarge => (502, "Upstream Response Too Large"),
2178 BackendError::UnsupportedUpgrade => (501, "HNS Protocol Upgrade Unsupported"),
2179 BackendError::Internal => (500, "Proxy Internal Error"),
2180 }
2181}
2182
2183fn connect_backend_error_status(error: BackendError) -> (u16, &'static str) {
2184 match error {
2185 BackendError::Cancelled => (503, "Browser Security Request Cancelled"),
2186 BackendError::InvalidRequest => (400, "Invalid Browser Security Request"),
2187 BackendError::PolicyDenied => (403, "Browser Security Policy Denied"),
2188 BackendError::ResolutionFailed => (502, "Namespace Resolution Failed"),
2189 BackendError::TlsValidationFailed => (502, "Origin TLS Validation Failed"),
2190 BackendError::UpstreamUnavailable => (502, "Origin Unavailable"),
2191 BackendError::InvalidResponse => (502, "Invalid Origin Response"),
2192 BackendError::ResponseTooLarge => (502, "Origin Response Too Large"),
2193 BackendError::UnsupportedUpgrade => (501, "Protocol Upgrade Unsupported"),
2194 BackendError::Internal => (503, "Browser Security Runtime Unavailable"),
2195 }
2196}
2197
2198fn backend_failure_kind(error: BackendError) -> BackendFailureKind {
2199 match error {
2200 BackendError::Cancelled => BackendFailureKind::Cancelled,
2201 BackendError::InvalidRequest => BackendFailureKind::InvalidRequest,
2202 BackendError::PolicyDenied => BackendFailureKind::PolicyDenied,
2203 BackendError::ResolutionFailed => BackendFailureKind::Resolution,
2204 BackendError::TlsValidationFailed => BackendFailureKind::TlsValidation,
2205 BackendError::UpstreamUnavailable => BackendFailureKind::Upstream,
2206 BackendError::InvalidResponse => BackendFailureKind::InvalidResponse,
2207 BackendError::ResponseTooLarge => BackendFailureKind::ResponseTooLarge,
2208 BackendError::UnsupportedUpgrade => BackendFailureKind::InvalidRequest,
2209 BackendError::Internal => BackendFailureKind::Internal,
2210 }
2211}
2212
2213fn rate_limit_config(limits: ProxyLimits) -> RateLimitConfig {
2214 RateLimitConfig {
2215 global_requests: limits.max_requests_per_window(),
2216 per_host_requests: limits.max_requests_per_host_per_window(),
2217 window: limits.rate_window(),
2218 max_tracked_hosts: limits.max_tracked_hosts(),
2219 }
2220}
2221
2222fn retry_after_seconds(duration: Duration) -> u64 {
2223 duration.as_secs() + u64::from(duration.subsec_nanos() != 0)
2224}
2225
2226fn write_error_response<W: Write + ?Sized>(
2227 stream: &mut W,
2228 status: u16,
2229 reason: &'static str,
2230 extra_headers: &[(&'static str, String)],
2231) -> io::Result<()> {
2232 let body = reason.as_bytes();
2233 let mut response = format!(
2234 "HTTP/1.1 {status} {reason}\r\nConnection: close\r\nCache-Control: no-store\r\nContent-Type: text/plain; charset=utf-8\r\nX-Content-Type-Options: nosniff\r\n"
2235 )
2236 .into_bytes();
2237 for (name, value) in extra_headers {
2238 response.extend_from_slice(name.as_bytes());
2239 response.extend_from_slice(b": ");
2240 response.extend_from_slice(value.as_bytes());
2241 response.extend_from_slice(b"\r\n");
2242 }
2243 response.extend_from_slice(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes());
2244 response.extend_from_slice(body);
2245 stream.write_all(&response)?;
2246 stream.flush()
2247}
2248
2249trait ClientIo: Read + Write {
2250 fn set_client_read_deadline(&mut self, deadline: Instant) -> io::Result<()>;
2251
2252 fn clear_client_read_deadline(&mut self) -> io::Result<()> {
2253 Ok(())
2254 }
2255}
2256
2257impl ClientIo for TcpStream {
2258 fn set_client_read_deadline(&mut self, deadline: Instant) -> io::Result<()> {
2259 let remaining = deadline
2260 .checked_duration_since(Instant::now())
2261 .filter(|remaining| !remaining.is_zero())
2262 .ok_or_else(|| io::Error::new(io::ErrorKind::TimedOut, "request deadline elapsed"))?;
2263 TcpStream::set_read_timeout(self, Some(remaining))
2264 }
2265
2266 fn clear_client_read_deadline(&mut self) -> io::Result<()> {
2267 TcpStream::set_read_timeout(self, None)
2268 }
2269}
2270
2271impl ClientIo for TlsStream {
2272 fn set_client_read_deadline(&mut self, deadline: Instant) -> io::Result<()> {
2273 self.sock.set_request_deadline(deadline);
2274 Ok(())
2275 }
2276
2277 fn clear_client_read_deadline(&mut self) -> io::Result<()> {
2278 self.sock.clear_request_deadline()
2279 }
2280}
2281
2282struct DeadlineReader<'a, S: ClientIo + ?Sized> {
2283 stream: &'a mut S,
2284 deadline: Instant,
2285}
2286
2287impl<'a, S: ClientIo + ?Sized> DeadlineReader<'a, S> {
2288 fn new(stream: &'a mut S, timeout: Duration) -> Self {
2289 let now = Instant::now();
2290 let deadline = now.checked_add(timeout).unwrap_or(now);
2291 Self { stream, deadline }
2292 }
2293}
2294
2295impl<S: ClientIo + ?Sized> Read for DeadlineReader<'_, S> {
2296 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2297 if Instant::now() >= self.deadline {
2298 return Err(io::Error::new(
2299 io::ErrorKind::TimedOut,
2300 "request deadline elapsed",
2301 ));
2302 }
2303 self.stream.set_client_read_deadline(self.deadline)?;
2304 self.stream.read(buffer)
2305 }
2306}
2307
2308fn observe_client_rejection(context: &ServerContext, reason: ClientRejectionReason) {
2309 observe(
2310 context.observer.as_ref(),
2311 ProxyEvent::ClientRejected {
2312 generation: context.generation,
2313 reason,
2314 },
2315 );
2316}
2317
2318fn observe_request(
2319 context: &ServerContext,
2320 host: &str,
2321 method: ObservedMethod,
2322 phase: RequestPhase,
2323) {
2324 let Ok(host) = ObservedHost::new(host) else {
2325 return;
2326 };
2327 observe(
2328 context.observer.as_ref(),
2329 ProxyEvent::Request {
2330 generation: context.generation,
2331 host,
2332 method,
2333 phase,
2334 },
2335 );
2336}
2337
2338fn observe_response_metadata(
2339 context: &ServerContext,
2340 host: &str,
2341 method: ObservedMethod,
2342 status_code: u16,
2343 likely_main_frame: bool,
2344 observation_id: Option<u64>,
2345 metadata: &crate::InternalResponseMetadata,
2346) {
2347 let Ok(host) = ObservedHost::new(host) else {
2348 return;
2349 };
2350 let observation = ProxyResponseMetadataObservation::new(
2351 context.generation,
2352 host,
2353 method,
2354 status_code,
2355 likely_main_frame,
2356 observation_id,
2357 ProxyMetadataObservationKind::OriginResponse,
2358 None,
2359 None,
2360 metadata.clone(),
2361 );
2362 let _result = catch_unwind(AssertUnwindSafe(|| {
2363 context.metadata_observer.observe(&observation);
2364 }));
2365}
2366
2367fn observe_connect_security_decision(
2368 context: &ServerContext,
2369 host: &str,
2370 port: u16,
2371 observation_id: Option<u64>,
2372 correlation_epoch: u64,
2373) {
2374 let Ok(host) = ObservedHost::new(host) else {
2375 return;
2376 };
2377 let observation = ProxyResponseMetadataObservation::new(
2378 context.generation,
2379 host,
2380 ObservedMethod::Connect,
2381 200,
2382 false,
2383 observation_id,
2384 ProxyMetadataObservationKind::WebPkiConnectDecision,
2385 Some(port),
2386 Some(correlation_epoch),
2387 crate::InternalResponseMetadata::default(),
2388 );
2389 let _result = catch_unwind(AssertUnwindSafe(|| {
2390 context.metadata_observer.observe(&observation);
2391 }));
2392}
2393
2394fn observe_proxy_generated_response(
2395 context: &ServerContext,
2396 host: &str,
2397 method: ObservedMethod,
2398 status_code: u16,
2399 likely_main_frame: bool,
2400) {
2401 if !likely_main_frame {
2402 return;
2403 }
2404 observe_response_metadata(
2405 context,
2406 host,
2407 method,
2408 status_code,
2409 true,
2410 None,
2411 &crate::InternalResponseMetadata::default(),
2412 );
2413}
2414
2415fn observe(observer: &dyn ProxyObserver, event: ProxyEvent) {
2416 let _result = catch_unwind(AssertUnwindSafe(|| observer.observe(&event)));
2417}
2418
2419#[cfg(test)]
2420mod tests {
2421 use super::*;
2422 use crate::{
2423 NoopProxyObserver, ProxyInstanceId, ProxyPublicationAuthority, ProxyPublicationPermit,
2424 ProxyResponseHead, ProxyResponseMetadataObservation, ProxySessionId, ProxyTunnel,
2425 };
2426 use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
2427 use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
2428 use rustls::{
2429 ClientConfig, ClientConnection, DigitallySignedStruct, Error as RustlsError,
2430 SignatureScheme, StreamOwned,
2431 };
2432 use std::collections::VecDeque;
2433 use std::io::Cursor;
2434 use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
2435 use std::sync::{Mutex, mpsc};
2436 use std::thread;
2437
2438 const TEST_TIMEOUT: Duration = Duration::from_secs(2);
2439
2440 fn test_config() -> ProxyConfig {
2441 ProxyConfig::new(
2442 ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 1),
2443 crate::HostScope::new("welcome").unwrap(),
2444 )
2445 }
2446
2447 struct UnusedBackend;
2448
2449 impl ProxyBackend for UnusedBackend {
2450 fn execute(
2451 &self,
2452 _request: ProxyRequest,
2453 _cancellation: &CancellationToken,
2454 ) -> Result<ProxyResponse, BackendError> {
2455 Err(BackendError::Internal)
2456 }
2457 }
2458
2459 #[derive(Clone)]
2460 enum ResponsePlan {
2461 Fixed {
2462 headers: Vec<ProxyHeader>,
2463 body: Vec<u8>,
2464 },
2465 ShortStream {
2466 expected_len: u64,
2467 bytes: Vec<u8>,
2468 },
2469 }
2470
2471 impl ResponsePlan {
2472 fn plain(body: impl Into<Vec<u8>>) -> Self {
2473 Self::Fixed {
2474 headers: vec![ProxyHeader::new("Content-Type", "text/plain")],
2475 body: body.into(),
2476 }
2477 }
2478
2479 fn response(&self) -> ProxyResponse {
2480 let (headers, body) = match self {
2481 Self::Fixed { headers, body } => {
2482 (headers.clone(), ProxyResponseBody::Bytes(body.clone()))
2483 }
2484 Self::ShortStream {
2485 expected_len,
2486 bytes,
2487 } => (
2488 vec![ProxyHeader::new("Content-Type", "application/octet-stream")],
2489 ProxyResponseBody::Stream {
2490 expected_len: *expected_len,
2491 reader: Box::new(Cursor::new(bytes.clone())),
2492 },
2493 ),
2494 };
2495 ProxyResponse {
2496 head: ProxyResponseHead {
2497 status_code: 200,
2498 reason_phrase: "OK".to_owned(),
2499 headers,
2500 observation_id: None,
2501 },
2502 body,
2503 publication_permit: Default::default(),
2504 }
2505 }
2506 }
2507
2508 struct RecordingBackend {
2509 requests: Mutex<Vec<ProxyRequest>>,
2510 response: ResponsePlan,
2511 }
2512
2513 impl RecordingBackend {
2514 fn new(response: ResponsePlan) -> Self {
2515 Self {
2516 requests: Mutex::new(Vec::new()),
2517 response,
2518 }
2519 }
2520
2521 fn request_count(&self) -> usize {
2522 self.requests
2523 .lock()
2524 .unwrap_or_else(|poisoned| poisoned.into_inner())
2525 .len()
2526 }
2527
2528 fn take_requests(&self) -> Vec<ProxyRequest> {
2529 std::mem::take(
2530 &mut *self
2531 .requests
2532 .lock()
2533 .unwrap_or_else(|poisoned| poisoned.into_inner()),
2534 )
2535 }
2536 }
2537
2538 impl ProxyBackend for RecordingBackend {
2539 fn execute(
2540 &self,
2541 request: ProxyRequest,
2542 _cancellation: &CancellationToken,
2543 ) -> Result<ProxyResponse, BackendError> {
2544 self.requests
2545 .lock()
2546 .unwrap_or_else(|poisoned| poisoned.into_inner())
2547 .push(request);
2548 Ok(self.response.response())
2549 }
2550 }
2551
2552 struct GatedPublicationAuthority {
2553 revoked: AtomicBool,
2554 entered: Mutex<Option<mpsc::Sender<()>>>,
2555 release: Mutex<mpsc::Receiver<()>>,
2556 }
2557
2558 impl ProxyPublicationAuthority for GatedPublicationAuthority {
2559 fn publish(&self, operation: &mut dyn FnMut() -> io::Result<()>) -> io::Result<()> {
2560 if let Some(entered) = self
2561 .entered
2562 .lock()
2563 .unwrap_or_else(|poisoned| poisoned.into_inner())
2564 .take()
2565 {
2566 let _result = entered.send(());
2567 }
2568 self.release
2569 .lock()
2570 .unwrap_or_else(|poisoned| poisoned.into_inner())
2571 .recv_timeout(TEST_TIMEOUT)
2572 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "test permit timed out"))?;
2573 if self.revoked.load(AtomicOrdering::Acquire) {
2574 return Err(io::Error::new(
2575 io::ErrorKind::ConnectionAborted,
2576 "test authority was revoked",
2577 ));
2578 }
2579 operation()
2580 }
2581 }
2582
2583 struct PublicationPermitBackend {
2584 permit: ProxyPublicationPermit,
2585 }
2586
2587 impl ProxyBackend for PublicationPermitBackend {
2588 fn execute(
2589 &self,
2590 _request: ProxyRequest,
2591 _cancellation: &CancellationToken,
2592 ) -> Result<ProxyResponse, BackendError> {
2593 let mut response = ResponsePlan::plain("must-not-publish").response();
2594 response.publication_permit = self.permit.clone();
2595 Ok(response)
2596 }
2597 }
2598
2599 struct PublicationPermitTunnelBackend {
2600 permit: ProxyPublicationPermit,
2601 }
2602
2603 impl ProxyBackend for PublicationPermitTunnelBackend {
2604 fn execute(
2605 &self,
2606 _request: ProxyRequest,
2607 _cancellation: &CancellationToken,
2608 ) -> Result<ProxyResponse, BackendError> {
2609 Err(BackendError::Internal)
2610 }
2611
2612 fn open_tunnel(
2613 &self,
2614 _request: ProxyRequest,
2615 _cancellation: &CancellationToken,
2616 ) -> Result<ProxyTunnelOpen, BackendError> {
2617 Ok(ProxyTunnelOpen::Tunnel(ProxyTunnel {
2618 head: ProxyResponseHead {
2619 status_code: 101,
2620 reason_phrase: "Switching Protocols".to_owned(),
2621 headers: vec![
2622 ProxyHeader::new("Connection", "Upgrade"),
2623 ProxyHeader::new("Upgrade", "websocket"),
2624 ProxyHeader::new("Sec-WebSocket-Accept", "accepted"),
2625 ],
2626 observation_id: None,
2627 },
2628 stream: Box::new(EchoTunnelStream {
2629 pending: VecDeque::new(),
2630 }),
2631 publication_permit: self.permit.clone(),
2632 }))
2633 }
2634 }
2635
2636 struct EchoTunnelBackend {
2637 requests: Mutex<Vec<ProxyRequest>>,
2638 head: ProxyResponseHead,
2639 initial_origin_bytes: Vec<u8>,
2640 }
2641
2642 impl EchoTunnelBackend {
2643 fn websocket(initial_origin_bytes: impl Into<Vec<u8>>) -> Self {
2644 Self {
2645 requests: Mutex::new(Vec::new()),
2646 head: ProxyResponseHead {
2647 status_code: 101,
2648 reason_phrase: "Switching Protocols".to_owned(),
2649 headers: vec![
2650 ProxyHeader::new("Connection", "keep-alive, Upgrade, X-Origin-Hop"),
2651 ProxyHeader::new("Upgrade", "websocket"),
2652 ProxyHeader::new("Sec-WebSocket-Accept", "accepted"),
2653 ProxyHeader::new("X-Origin-Hop", "secret"),
2654 ProxyHeader::new("X-HNS-Security-Path", "secret"),
2655 ],
2656 observation_id: None,
2657 },
2658 initial_origin_bytes: initial_origin_bytes.into(),
2659 }
2660 }
2661
2662 fn with_head(head: ProxyResponseHead) -> Self {
2663 Self {
2664 requests: Mutex::new(Vec::new()),
2665 head,
2666 initial_origin_bytes: Vec::new(),
2667 }
2668 }
2669
2670 fn take_requests(&self) -> Vec<ProxyRequest> {
2671 std::mem::take(
2672 &mut *self
2673 .requests
2674 .lock()
2675 .unwrap_or_else(|poisoned| poisoned.into_inner()),
2676 )
2677 }
2678 }
2679
2680 impl ProxyBackend for EchoTunnelBackend {
2681 fn execute(
2682 &self,
2683 _request: ProxyRequest,
2684 _cancellation: &CancellationToken,
2685 ) -> Result<ProxyResponse, BackendError> {
2686 Err(BackendError::Internal)
2687 }
2688
2689 fn open_tunnel(
2690 &self,
2691 request: ProxyRequest,
2692 _cancellation: &CancellationToken,
2693 ) -> Result<ProxyTunnelOpen, BackendError> {
2694 self.requests
2695 .lock()
2696 .unwrap_or_else(|poisoned| poisoned.into_inner())
2697 .push(request);
2698 Ok(ProxyTunnelOpen::Tunnel(ProxyTunnel {
2699 head: self.head.clone(),
2700 stream: Box::new(EchoTunnelStream {
2701 pending: self.initial_origin_bytes.iter().copied().collect(),
2702 }),
2703 publication_permit: Default::default(),
2704 }))
2705 }
2706 }
2707
2708 struct EchoTunnelStream {
2709 pending: VecDeque<u8>,
2710 }
2711
2712 impl Read for EchoTunnelStream {
2713 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2714 if self.pending.is_empty() {
2715 thread::sleep(Duration::from_millis(5));
2716 return Err(io::Error::new(io::ErrorKind::TimedOut, "idle test tunnel"));
2717 }
2718 let count = buffer.len().min(self.pending.len());
2719 for output in &mut buffer[..count] {
2720 *output = self.pending.pop_front().unwrap();
2721 }
2722 Ok(count)
2723 }
2724 }
2725
2726 impl Write for EchoTunnelStream {
2727 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
2728 self.pending.extend(buffer.iter().copied());
2729 Ok(buffer.len())
2730 }
2731
2732 fn flush(&mut self) -> io::Result<()> {
2733 Ok(())
2734 }
2735 }
2736
2737 struct ConnectPassthroughBackend {
2738 requests: Mutex<Vec<ProxyRequest>>,
2739 }
2740
2741 struct ConnectEchoState {
2742 pending: Mutex<VecDeque<u8>>,
2743 shutdown: AtomicBool,
2744 }
2745
2746 struct ConnectEchoReader(Arc<ConnectEchoState>);
2747
2748 impl Read for ConnectEchoReader {
2749 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2750 let mut pending = self
2751 .0
2752 .pending
2753 .lock()
2754 .unwrap_or_else(|poisoned| poisoned.into_inner());
2755 if pending.is_empty() {
2756 if self.0.shutdown.load(AtomicOrdering::Acquire) {
2757 return Ok(0);
2758 }
2759 drop(pending);
2760 thread::sleep(Duration::from_millis(5));
2761 return Err(io::Error::new(io::ErrorKind::TimedOut, "idle test tunnel"));
2762 }
2763 let count = buffer.len().min(pending.len());
2764 for output in &mut buffer[..count] {
2765 *output = pending.pop_front().unwrap();
2766 }
2767 Ok(count)
2768 }
2769 }
2770
2771 struct ConnectEchoWriter(Arc<ConnectEchoState>);
2772
2773 impl Write for ConnectEchoWriter {
2774 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
2775 if self.0.shutdown.load(AtomicOrdering::Acquire) {
2776 return Err(io::Error::new(
2777 io::ErrorKind::BrokenPipe,
2778 "test tunnel was shut down",
2779 ));
2780 }
2781 self.0
2782 .pending
2783 .lock()
2784 .unwrap_or_else(|poisoned| poisoned.into_inner())
2785 .extend(buffer.iter().copied());
2786 Ok(buffer.len())
2787 }
2788
2789 fn flush(&mut self) -> io::Result<()> {
2790 Ok(())
2791 }
2792 }
2793
2794 struct ConnectEchoShutdown(Arc<ConnectEchoState>);
2795
2796 impl crate::ProxyConnectShutdown for ConnectEchoShutdown {
2797 fn shutdown(&self) {
2798 self.0.shutdown.store(true, AtomicOrdering::Release);
2799 }
2800 }
2801
2802 struct NoopConnectShutdown;
2803
2804 impl crate::ProxyConnectShutdown for NoopConnectShutdown {
2805 fn shutdown(&self) {}
2806 }
2807
2808 struct BulkDownloadBackend {
2809 bytes: Vec<u8>,
2810 }
2811
2812 impl ProxyBackend for BulkDownloadBackend {
2813 fn open_connect(
2814 &self,
2815 _request: ProxyRequest,
2816 _cancellation: &CancellationToken,
2817 ) -> Result<ProxyConnectOpen, BackendError> {
2818 Ok(ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
2819 reader: Box::new(Cursor::new(self.bytes.clone())),
2820 writer: Box::new(io::sink()),
2821 shutdown: Arc::new(NoopConnectShutdown),
2822 observation_id: None,
2823 correlation_epoch: 1,
2824 publication_permit: ProxyPublicationPermit::default(),
2825 }))
2826 }
2827
2828 fn execute(
2829 &self,
2830 _request: ProxyRequest,
2831 _cancellation: &CancellationToken,
2832 ) -> Result<ProxyResponse, BackendError> {
2833 Err(BackendError::Internal)
2834 }
2835 }
2836
2837 struct BulkUploadState {
2838 received: std::sync::atomic::AtomicUsize,
2839 shutdown: AtomicBool,
2840 }
2841
2842 struct IdleOriginReader(Arc<BulkUploadState>);
2843
2844 impl Read for IdleOriginReader {
2845 fn read(&mut self, _buffer: &mut [u8]) -> io::Result<usize> {
2846 if self.0.shutdown.load(AtomicOrdering::Acquire) {
2847 return Ok(0);
2848 }
2849 thread::sleep(Duration::from_millis(25));
2850 Err(io::Error::new(
2851 io::ErrorKind::TimedOut,
2852 "idle origin test reader",
2853 ))
2854 }
2855 }
2856
2857 struct CountingOriginWriter(Arc<BulkUploadState>);
2858
2859 impl Write for CountingOriginWriter {
2860 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
2861 self.0
2862 .received
2863 .fetch_add(buffer.len(), AtomicOrdering::AcqRel);
2864 Ok(buffer.len())
2865 }
2866
2867 fn flush(&mut self) -> io::Result<()> {
2868 Ok(())
2869 }
2870 }
2871
2872 struct BulkUploadShutdown(Arc<BulkUploadState>);
2873
2874 impl crate::ProxyConnectShutdown for BulkUploadShutdown {
2875 fn shutdown(&self) {
2876 self.0.shutdown.store(true, AtomicOrdering::Release);
2877 }
2878 }
2879
2880 struct BulkUploadBackend {
2881 state: Arc<BulkUploadState>,
2882 }
2883
2884 impl ProxyBackend for BulkUploadBackend {
2885 fn open_connect(
2886 &self,
2887 _request: ProxyRequest,
2888 _cancellation: &CancellationToken,
2889 ) -> Result<ProxyConnectOpen, BackendError> {
2890 Ok(ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
2891 reader: Box::new(IdleOriginReader(Arc::clone(&self.state))),
2892 writer: Box::new(CountingOriginWriter(Arc::clone(&self.state))),
2893 shutdown: Arc::new(BulkUploadShutdown(Arc::clone(&self.state))),
2894 observation_id: None,
2895 correlation_epoch: 1,
2896 publication_permit: ProxyPublicationPermit::default(),
2897 }))
2898 }
2899
2900 fn execute(
2901 &self,
2902 _request: ProxyRequest,
2903 _cancellation: &CancellationToken,
2904 ) -> Result<ProxyResponse, BackendError> {
2905 Err(BackendError::Internal)
2906 }
2907 }
2908
2909 struct WebPkiUnavailableBackend;
2910
2911 impl ProxyBackend for WebPkiUnavailableBackend {
2912 fn open_connect(
2913 &self,
2914 _request: ProxyRequest,
2915 _cancellation: &CancellationToken,
2916 ) -> Result<ProxyConnectOpen, BackendError> {
2917 Ok(ProxyConnectOpen::WebPkiUnavailable)
2918 }
2919
2920 fn execute(
2921 &self,
2922 _request: ProxyRequest,
2923 _cancellation: &CancellationToken,
2924 ) -> Result<ProxyResponse, BackendError> {
2925 Err(BackendError::Internal)
2926 }
2927 }
2928
2929 struct UnexpectedConnectFailureBackend {
2930 panic: bool,
2931 }
2932
2933 impl ProxyBackend for UnexpectedConnectFailureBackend {
2934 fn open_connect(
2935 &self,
2936 _request: ProxyRequest,
2937 _cancellation: &CancellationToken,
2938 ) -> Result<ProxyConnectOpen, BackendError> {
2939 if self.panic {
2940 panic!("test CONNECT backend panic");
2941 }
2942 Err(BackendError::Internal)
2943 }
2944
2945 fn execute(
2946 &self,
2947 _request: ProxyRequest,
2948 _cancellation: &CancellationToken,
2949 ) -> Result<ProxyResponse, BackendError> {
2950 Err(BackendError::Internal)
2951 }
2952 }
2953
2954 impl ProxyBackend for ConnectPassthroughBackend {
2955 fn open_connect(
2956 &self,
2957 request: ProxyRequest,
2958 _cancellation: &CancellationToken,
2959 ) -> Result<ProxyConnectOpen, BackendError> {
2960 self.requests
2961 .lock()
2962 .unwrap_or_else(|poisoned| poisoned.into_inner())
2963 .push(request);
2964 let state = Arc::new(ConnectEchoState {
2965 pending: Mutex::new(b"origin".iter().copied().collect()),
2966 shutdown: AtomicBool::new(false),
2967 });
2968 Ok(ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
2969 reader: Box::new(ConnectEchoReader(Arc::clone(&state))),
2970 writer: Box::new(ConnectEchoWriter(Arc::clone(&state))),
2971 shutdown: Arc::new(ConnectEchoShutdown(state)),
2972 observation_id: Some(17),
2973 correlation_epoch: 9,
2974 publication_permit: ProxyPublicationPermit::default(),
2975 }))
2976 }
2977
2978 fn execute(
2979 &self,
2980 _request: ProxyRequest,
2981 _cancellation: &CancellationToken,
2982 ) -> Result<ProxyResponse, BackendError> {
2983 Err(BackendError::Internal)
2984 }
2985 }
2986
2987 struct HttpRejectingTunnelBackend;
2988
2989 impl ProxyBackend for HttpRejectingTunnelBackend {
2990 fn execute(
2991 &self,
2992 _request: ProxyRequest,
2993 _cancellation: &CancellationToken,
2994 ) -> Result<ProxyResponse, BackendError> {
2995 Err(BackendError::Internal)
2996 }
2997
2998 fn open_tunnel(
2999 &self,
3000 _request: ProxyRequest,
3001 _cancellation: &CancellationToken,
3002 ) -> Result<ProxyTunnelOpen, BackendError> {
3003 Ok(ProxyTunnelOpen::Response(ProxyResponse {
3004 head: ProxyResponseHead {
3005 status_code: 404,
3006 reason_phrase: "HNS Name Not Found".to_owned(),
3007 headers: vec![
3008 ProxyHeader::new("Content-Type", "text/plain"),
3009 ProxyHeader::new("X-HNS-Security-Path", "verified-non-inclusion"),
3010 ],
3011 observation_id: None,
3012 },
3013 body: ProxyResponseBody::Bytes(b"verified non-inclusion".to_vec()),
3014 publication_permit: Default::default(),
3015 }))
3016 }
3017 }
3018
3019 struct CooperativeStreamBackend {
3020 read_started: Mutex<Option<mpsc::Sender<()>>>,
3021 }
3022
3023 impl ProxyBackend for CooperativeStreamBackend {
3024 fn execute(
3025 &self,
3026 _request: ProxyRequest,
3027 cancellation: &CancellationToken,
3028 ) -> Result<ProxyResponse, BackendError> {
3029 let read_started = self
3030 .read_started
3031 .lock()
3032 .unwrap_or_else(|poisoned| poisoned.into_inner())
3033 .take();
3034 Ok(ProxyResponse {
3035 head: ProxyResponseHead {
3036 status_code: 200,
3037 reason_phrase: "OK".to_owned(),
3038 headers: vec![],
3039 observation_id: None,
3040 },
3041 body: ProxyResponseBody::Stream {
3042 expected_len: 1,
3043 reader: Box::new(CooperativeCancellationReader {
3044 cancellation: cancellation.clone(),
3045 read_started,
3046 }),
3047 },
3048 publication_permit: Default::default(),
3049 })
3050 }
3051 }
3052
3053 struct CooperativeCancellationReader {
3054 cancellation: CancellationToken,
3055 read_started: Option<mpsc::Sender<()>>,
3056 }
3057
3058 impl Read for CooperativeCancellationReader {
3059 fn read(&mut self, _buffer: &mut [u8]) -> io::Result<usize> {
3060 if let Some(read_started) = self.read_started.take() {
3061 let _result = read_started.send(());
3062 }
3063 if self
3064 .cancellation
3065 .wait_cancelled_timeout(Duration::from_secs(5))
3066 {
3067 Err(io::Error::new(
3068 io::ErrorKind::Interrupted,
3069 "proxy generation cancelled",
3070 ))
3071 } else {
3072 Err(io::Error::new(
3073 io::ErrorKind::TimedOut,
3074 "test cancellation did not arrive",
3075 ))
3076 }
3077 }
3078 }
3079
3080 struct AcceptedObserver(mpsc::Sender<()>);
3081
3082 impl ProxyObserver for AcceptedObserver {
3083 fn observe(&self, event: &ProxyEvent) {
3084 if matches!(
3085 event,
3086 ProxyEvent::Request {
3087 phase: RequestPhase::Accepted,
3088 ..
3089 }
3090 ) {
3091 let _result = self.0.send(());
3092 }
3093 }
3094 }
3095
3096 fn dane_browser_config() -> ProxyConfig {
3097 ProxyConfig::dane_browser(ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 1))
3098 }
3099
3100 fn start_recording_proxy(backend: Arc<RecordingBackend>) -> RunningProxy {
3101 RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap()
3102 }
3103
3104 fn start_recording_proxy_with_metadata(
3105 backend: Arc<RecordingBackend>,
3106 metadata_observer: Arc<dyn ProxyResponseMetadataObserver>,
3107 ) -> RunningProxy {
3108 RunningProxy::start_with_metadata_observer(
3109 test_config(),
3110 backend,
3111 Arc::new(NoopProxyObserver),
3112 metadata_observer,
3113 )
3114 .unwrap()
3115 }
3116
3117 fn send_raw(proxy: &RunningProxy, request: &[u8]) -> Vec<u8> {
3118 let mut stream = TcpStream::connect(proxy.endpoint().address()).unwrap();
3119 stream.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3120 stream.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
3121 stream.write_all(request).unwrap();
3122 stream.shutdown(Shutdown::Write).unwrap();
3123 let mut response = Vec::new();
3124 match stream.read_to_end(&mut response) {
3125 Ok(_) => {}
3126 Err(error) if error.kind() == io::ErrorKind::ConnectionReset => {}
3127 Err(error) => panic!("unable to read proxy response: {error}"),
3128 }
3129 response
3130 }
3131
3132 fn response_status(response: &[u8]) -> u16 {
3133 let line_end = response
3134 .windows(2)
3135 .position(|pair| pair == b"\r\n")
3136 .expect("response status line ends");
3137 std::str::from_utf8(&response[..line_end])
3138 .unwrap()
3139 .split_whitespace()
3140 .nth(1)
3141 .unwrap()
3142 .parse()
3143 .unwrap()
3144 }
3145
3146 fn response_parts(response: &[u8]) -> (&str, &[u8]) {
3147 let head_end = response
3148 .windows(4)
3149 .position(|window| window == b"\r\n\r\n")
3150 .expect("response head terminates");
3151 (
3152 std::str::from_utf8(&response[..head_end + 4]).unwrap(),
3153 &response[head_end + 4..],
3154 )
3155 }
3156
3157 fn auth_header(proxy: &RunningProxy) -> String {
3158 format!(
3159 "Proxy-Authorization: {}\r\n",
3160 proxy.endpoint().authorization_header_value()
3161 )
3162 }
3163
3164 #[derive(Debug)]
3165 struct CapturingCertificateVerifier {
3166 certificate_der: Arc<Mutex<Option<Vec<u8>>>>,
3167 }
3168
3169 impl ServerCertVerifier for CapturingCertificateVerifier {
3170 fn verify_server_cert(
3171 &self,
3172 end_entity: &CertificateDer<'_>,
3173 _intermediates: &[CertificateDer<'_>],
3174 _server_name: &ServerName<'_>,
3175 _ocsp_response: &[u8],
3176 _now: UnixTime,
3177 ) -> Result<ServerCertVerified, RustlsError> {
3178 *self
3179 .certificate_der
3180 .lock()
3181 .unwrap_or_else(|poisoned| poisoned.into_inner()) =
3182 Some(end_entity.as_ref().to_vec());
3183 Ok(ServerCertVerified::assertion())
3184 }
3185
3186 fn verify_tls12_signature(
3187 &self,
3188 message: &[u8],
3189 certificate: &CertificateDer<'_>,
3190 signature: &DigitallySignedStruct,
3191 ) -> Result<HandshakeSignatureValid, RustlsError> {
3192 rustls::crypto::verify_tls12_signature(
3193 message,
3194 certificate,
3195 signature,
3196 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
3197 )
3198 }
3199
3200 fn verify_tls13_signature(
3201 &self,
3202 message: &[u8],
3203 certificate: &CertificateDer<'_>,
3204 signature: &DigitallySignedStruct,
3205 ) -> Result<HandshakeSignatureValid, RustlsError> {
3206 rustls::crypto::verify_tls13_signature(
3207 message,
3208 certificate,
3209 signature,
3210 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
3211 )
3212 }
3213
3214 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
3215 rustls::crypto::ring::default_provider()
3216 .signature_verification_algorithms
3217 .supported_schemes()
3218 }
3219 }
3220
3221 fn tls_client_config(
3222 certificate_der: Arc<Mutex<Option<Vec<u8>>>>,
3223 alpn_protocols: &[&[u8]],
3224 enable_sni: bool,
3225 ) -> Arc<ClientConfig> {
3226 let mut config =
3227 ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
3228 .with_safe_default_protocol_versions()
3229 .unwrap()
3230 .dangerous()
3231 .with_custom_certificate_verifier(Arc::new(CapturingCertificateVerifier {
3232 certificate_der,
3233 }))
3234 .with_no_client_auth();
3235 config.enable_sni = enable_sni;
3236 config.alpn_protocols = alpn_protocols
3237 .iter()
3238 .map(|protocol| protocol.to_vec())
3239 .collect();
3240 Arc::new(config)
3241 }
3242
3243 fn read_response_head(input: &mut impl Read) -> io::Result<Vec<u8>> {
3244 let mut response = Vec::new();
3245 let mut byte = [0_u8; 1];
3246 while response.len() < ProxyLimits::DEFAULT_MAX_HEADER_BYTES {
3247 if input.read(&mut byte)? == 0 {
3248 return Err(io::Error::new(
3249 io::ErrorKind::UnexpectedEof,
3250 "response ended before its head",
3251 ));
3252 }
3253 response.push(byte[0]);
3254 if response.ends_with(b"\r\n\r\n") {
3255 return Ok(response);
3256 }
3257 }
3258 Err(io::Error::new(
3259 io::ErrorKind::InvalidData,
3260 "response head exceeded the test limit",
3261 ))
3262 }
3263
3264 fn begin_authenticated_connect(proxy: &RunningProxy, authority: &str) -> TcpStream {
3265 let mut stream = TcpStream::connect(proxy.endpoint().address()).unwrap();
3266 stream.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3267 stream.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
3268 let request = format!(
3269 "CONNECT {authority} HTTP/1.1\r\nHost: {authority}\r\n{}\r\n",
3270 auth_header(proxy)
3271 );
3272 stream.write_all(request.as_bytes()).unwrap();
3273 stream.flush().unwrap();
3274 let response = read_response_head(&mut stream).unwrap();
3275 assert_eq!(response_status(&response), 200, "{response:?}");
3276 stream
3277 }
3278
3279 fn complete_tls_handshake(
3280 stream: TcpStream,
3281 server_name: &str,
3282 enable_sni: bool,
3283 alpn_protocols: &[&[u8]],
3284 ) -> io::Result<(StreamOwned<ClientConnection, TcpStream>, Vec<u8>)> {
3285 let certificate_der = Arc::new(Mutex::new(None));
3286 let config = tls_client_config(Arc::clone(&certificate_der), alpn_protocols, enable_sni);
3287 let server_name = ServerName::try_from(server_name.to_owned())
3288 .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error.to_string()))?;
3289 let connection = ClientConnection::new(config, server_name)
3290 .map_err(|error| io::Error::other(error.to_string()))?;
3291 let mut tls = StreamOwned::new(connection, stream);
3292 while tls.conn.is_handshaking() {
3293 tls.conn.complete_io(&mut tls.sock)?;
3294 }
3295 let certificate_der = certificate_der
3296 .lock()
3297 .unwrap_or_else(|poisoned| poisoned.into_inner())
3298 .clone()
3299 .ok_or_else(|| io::Error::other("server did not present a certificate"))?;
3300 Ok((tls, certificate_der))
3301 }
3302
3303 fn send_tls_request(
3304 proxy: &RunningProxy,
3305 authority: &str,
3306 certificate_host: &str,
3307 request: &[u8],
3308 ) -> (Vec<u8>, Vec<u8>) {
3309 let stream = begin_authenticated_connect(proxy, authority);
3310 assert!(proxy.local_certificate_pin(certificate_host).is_some());
3311 let (mut tls, certificate_der) =
3312 complete_tls_handshake(stream, certificate_host, true, &[b"h2", b"http/1.1"]).unwrap();
3313 assert_eq!(tls.conn.alpn_protocol(), Some(b"http/1.1".as_slice()));
3314 tls.write_all(request).unwrap();
3315 tls.flush().unwrap();
3316 let mut response = Vec::new();
3317 tls.read_to_end(&mut response).unwrap();
3318 (response, certificate_der)
3319 }
3320
3321 fn wait_for_active_clients(proxy: &RunningProxy, expected: usize) {
3322 let deadline = Instant::now() + TEST_TIMEOUT;
3323 while proxy.active_clients() != expected && Instant::now() < deadline {
3324 thread::yield_now();
3325 }
3326 assert_eq!(proxy.active_clients(), expected);
3327 }
3328
3329 fn assert_connection_closed(mut stream: TcpStream) {
3330 stream.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3331 let mut byte = [0_u8; 1];
3332 match stream.read(&mut byte) {
3333 Ok(0) => {}
3334 Err(error)
3335 if matches!(
3336 error.kind(),
3337 io::ErrorKind::ConnectionReset
3338 | io::ErrorKind::NotConnected
3339 | io::ErrorKind::UnexpectedEof
3340 ) => {}
3341 result => panic!("expected a closed proxy connection, got {result:?}"),
3342 }
3343 }
3344
3345 fn parsed_test_head(method: &str, headers: &[(&str, &str)]) -> RequestHead {
3346 let mut request = format!("{method} http://welcome/ HTTP/1.1\r\nHost: welcome\r\n");
3347 for (name, value) in headers {
3348 request.push_str(name);
3349 request.push_str(": ");
3350 request.push_str(value);
3351 request.push_str("\r\n");
3352 }
3353 request.push_str("\r\n");
3354 crate::parse_request_head(request.as_bytes()).unwrap()
3355 }
3356
3357 #[test]
3358 fn likely_main_frame_classification_treats_fetch_destination_as_authoritative() {
3359 let classify = |method, path, headers: &[(&str, &str)]| {
3360 is_likely_main_frame_navigation(&parsed_test_head(method, headers), path)
3361 };
3362
3363 assert!(classify(
3364 "GET",
3365 "/asset.js",
3366 &[("Sec-Fetch-Dest", "document")]
3367 ));
3368 assert!(!classify(
3369 "HEAD",
3370 "/asset.js",
3371 &[
3372 ("Sec-Fetch-Dest", "IFRAME"),
3373 ("Sec-Fetch-Mode", "navigate"),
3374 ("Accept", "text/html"),
3375 ]
3376 ));
3377 assert!(!classify(
3378 "GET",
3379 "/",
3380 &[
3381 ("Sec-Fetch-Dest", "script"),
3382 ("Sec-Fetch-Mode", "navigate"),
3383 ("Accept", "text/html"),
3384 ],
3385 ));
3386 assert!(!classify(
3387 "GET",
3388 "/",
3389 &[
3390 ("Sec-Fetch-Dest", "future-subresource"),
3391 ("Sec-Fetch-Mode", "navigate"),
3392 ("Upgrade-Insecure-Requests", "1"),
3393 ("Accept", "text/html"),
3394 ],
3395 ));
3396 assert!(classify(
3397 "GET",
3398 "/asset.js",
3399 &[("Sec-Fetch-Mode", "navigate")],
3400 ));
3401 assert!(classify(
3402 "GET",
3403 "/asset.js",
3404 &[("Upgrade-Insecure-Requests", "1")],
3405 ));
3406 assert!(classify(
3407 "GET",
3408 "/asset.js",
3409 &[("Accept", "application/xhtml+xml, TEXT/HTML;q=0.9")],
3410 ));
3411 assert!(classify("GET", "/", &[]));
3412 assert!(!classify("GET", "/?private=1", &[]));
3413 assert!(!classify("GET", "/asset.css", &[]));
3414 assert!(!classify("POST", "/", &[("Sec-Fetch-Dest", "document")],));
3415 }
3416
3417 #[test]
3418 fn starts_on_ephemeral_loopback_with_fresh_redacted_credentials() {
3419 let first = RunningProxy::start(
3420 test_config(),
3421 Arc::new(UnusedBackend),
3422 Arc::new(NoopProxyObserver),
3423 )
3424 .unwrap();
3425 let second = RunningProxy::start(
3426 test_config(),
3427 Arc::new(UnusedBackend),
3428 Arc::new(NoopProxyObserver),
3429 )
3430 .unwrap();
3431
3432 assert!(first.endpoint().address().ip().is_loopback());
3433 assert_ne!(first.endpoint().port(), 0);
3434 assert_ne!(first.endpoint().realm(), second.endpoint().realm());
3435 assert_ne!(first.endpoint().password(), second.endpoint().password());
3436 let debug = format!("{first:?}");
3437 assert!(!debug.contains(first.endpoint().realm()));
3438 assert!(!debug.contains(first.endpoint().password()));
3439 first.stop();
3440 first.stop();
3441 second.stop();
3442 assert!(first.is_stopped());
3443 }
3444
3445 #[test]
3446 fn request_stop_revokes_admission_before_the_blocking_join() {
3447 let proxy = RunningProxy::start(
3448 test_config(),
3449 Arc::new(UnusedBackend),
3450 Arc::new(NoopProxyObserver),
3451 )
3452 .unwrap();
3453
3454 proxy.request_stop();
3455 assert!(proxy.local_certificate_pin("welcome").is_none());
3456 assert!(!proxy.matches_local_certificate("welcome", b"certificate"));
3457
3458 proxy.stop();
3459 assert!(proxy.is_stopped());
3460 }
3461
3462 #[test]
3463 fn concurrent_stop_emits_lifecycle_events_once_and_in_order() {
3464 let events = Arc::new(Mutex::new(Vec::new()));
3465 let event_sink = Arc::clone(&events);
3466 let observer = move |event: &ProxyEvent| {
3467 if let ProxyEvent::Lifecycle { event, .. } = event {
3468 event_sink
3469 .lock()
3470 .unwrap_or_else(|poisoned| poisoned.into_inner())
3471 .push(*event);
3472 }
3473 };
3474 let proxy = Arc::new(
3475 RunningProxy::start(test_config(), Arc::new(UnusedBackend), Arc::new(observer))
3476 .unwrap(),
3477 );
3478 let barrier = Arc::new(std::sync::Barrier::new(9));
3479 let callers: Vec<_> = (0..8)
3480 .map(|_| {
3481 let proxy = Arc::clone(&proxy);
3482 let barrier = Arc::clone(&barrier);
3483 thread::spawn(move || {
3484 barrier.wait();
3485 proxy.stop();
3486 })
3487 })
3488 .collect();
3489 barrier.wait();
3490 for caller in callers {
3491 caller.join().unwrap();
3492 }
3493
3494 assert!(proxy.is_stopped());
3495 assert_eq!(
3496 *events
3497 .lock()
3498 .unwrap_or_else(|poisoned| poisoned.into_inner()),
3499 vec![
3500 LifecycleEvent::Listening {
3501 port: proxy.endpoint().port(),
3502 },
3503 LifecycleEvent::Stopping,
3504 LifecycleEvent::Stopped {
3505 reason: StopReason::Requested,
3506 },
3507 ]
3508 );
3509 }
3510
3511 #[test]
3512 fn stopping_observer_can_reenter_running_proxy_stop() {
3513 let proxy_slot = Arc::new(Mutex::new(None::<std::sync::Weak<RunningProxy>>));
3514 let observer_slot = Arc::clone(&proxy_slot);
3515 let observer = move |event: &ProxyEvent| {
3516 if matches!(
3517 event,
3518 ProxyEvent::Lifecycle {
3519 event: LifecycleEvent::Stopping,
3520 ..
3521 }
3522 ) {
3523 let proxy = observer_slot
3524 .lock()
3525 .unwrap_or_else(|poisoned| poisoned.into_inner())
3526 .as_ref()
3527 .and_then(std::sync::Weak::upgrade);
3528 if let Some(proxy) = proxy {
3529 proxy.stop();
3530 }
3531 }
3532 };
3533 let proxy = Arc::new(
3534 RunningProxy::start(test_config(), Arc::new(UnusedBackend), Arc::new(observer))
3535 .unwrap(),
3536 );
3537 *proxy_slot
3538 .lock()
3539 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(Arc::downgrade(&proxy));
3540
3541 proxy.stop();
3542
3543 assert!(proxy.is_stopped());
3544 assert_eq!(proxy.active_clients(), 0);
3545 }
3546
3547 #[test]
3548 fn final_running_proxy_owner_dropped_from_callback_is_reaped_and_stopped() {
3549 let proxy_slot = Arc::new(Mutex::new(None::<std::sync::Weak<RunningProxy>>));
3550 let observer_slot = Arc::clone(&proxy_slot);
3551 let (upgraded_tx, upgraded_rx) = mpsc::channel();
3552 let (release_tx, release_rx) = mpsc::channel();
3553 let release_rx = Mutex::new(release_rx);
3554 let (released_tx, released_rx) = mpsc::channel();
3555 let (stopped_tx, stopped_rx) = mpsc::channel();
3556 let events = Arc::new(Mutex::new(Vec::new()));
3557 let event_sink = Arc::clone(&events);
3558 let observer = move |event: &ProxyEvent| {
3559 if let ProxyEvent::Lifecycle { event, .. } = event {
3560 event_sink
3561 .lock()
3562 .unwrap_or_else(|poisoned| poisoned.into_inner())
3563 .push(*event);
3564 if matches!(event, LifecycleEvent::Stopped { .. }) {
3565 let _result = stopped_tx.send(());
3566 }
3567 return;
3568 }
3569 if matches!(
3570 event,
3571 ProxyEvent::Request {
3572 phase: RequestPhase::Accepted,
3573 ..
3574 }
3575 ) {
3576 let proxy = observer_slot
3577 .lock()
3578 .unwrap_or_else(|poisoned| poisoned.into_inner())
3579 .as_ref()
3580 .and_then(std::sync::Weak::upgrade)
3581 .unwrap();
3582 upgraded_tx.send(()).unwrap();
3583 release_rx
3584 .lock()
3585 .unwrap_or_else(|poisoned| poisoned.into_inner())
3586 .recv_timeout(TEST_TIMEOUT)
3587 .unwrap();
3588 drop(proxy);
3589 released_tx.send(()).unwrap();
3590 }
3591 };
3592 let proxy = Arc::new(
3593 RunningProxy::start(test_config(), Arc::new(UnusedBackend), Arc::new(observer))
3594 .unwrap(),
3595 );
3596 *proxy_slot
3597 .lock()
3598 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(Arc::downgrade(&proxy));
3599 let address = proxy.endpoint().address();
3600 let request = format!(
3601 "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3602 auth_header(&proxy)
3603 );
3604 let mut client = TcpStream::connect(address).unwrap();
3605 client.write_all(request.as_bytes()).unwrap();
3606 client.flush().unwrap();
3607 upgraded_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3608
3609 drop(proxy);
3610 release_tx.send(()).unwrap();
3611 released_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3612 drop(client);
3613 stopped_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3614 assert_eq!(
3615 *events
3616 .lock()
3617 .unwrap_or_else(|poisoned| poisoned.into_inner()),
3618 vec![
3619 LifecycleEvent::Listening {
3620 port: address.port(),
3621 },
3622 LifecycleEvent::Stopping,
3623 LifecycleEvent::Stopped {
3624 reason: StopReason::Requested,
3625 },
3626 ]
3627 );
3628
3629 let deadline = Instant::now() + TEST_TIMEOUT;
3630 loop {
3631 match std::net::TcpListener::bind(address) {
3632 Ok(listener) => {
3633 drop(listener);
3634 break;
3635 }
3636 Err(_) => {
3637 assert!(Instant::now() < deadline);
3638 thread::sleep(Duration::from_millis(5));
3639 }
3640 }
3641 }
3642 }
3643
3644 #[test]
3645 fn listener_failure_revokes_pins_and_reports_quiescence_once() {
3646 let instance = ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 71);
3647 let identities = Arc::new(LocalTlsIdentityStore::new(instance));
3648 let host = crate::NormalizedHost::parse("welcome").unwrap();
3649 drop(identities.prepare(&host).unwrap());
3650 assert!(identities.pin(&host).is_some());
3651
3652 let events = Arc::new(Mutex::new(Vec::new()));
3653 let event_sink = Arc::clone(&events);
3654 let observer: Arc<dyn ProxyObserver> = Arc::new(move |event: &ProxyEvent| {
3655 if let ProxyEvent::Lifecycle { event, .. } = event {
3656 event_sink
3657 .lock()
3658 .unwrap_or_else(|poisoned| poisoned.into_inner())
3659 .push(*event);
3660 }
3661 });
3662 let stop_state = Arc::new(ProxyStopState::new());
3663 stop_state.publish_listening();
3664 let handler = listener_exit_handler(
3665 Arc::clone(&identities),
3666 Arc::clone(&observer),
3667 71,
3668 Arc::clone(&stop_state),
3669 );
3670
3671 handler(ListenerExitPhase::FailureDetected);
3672 assert!(stop_state.requested.load(Ordering::Acquire));
3673 assert!(!stop_state.completed.load(Ordering::Acquire));
3674 assert!(identities.pin(&host).is_none());
3675 assert_eq!(
3676 *events
3677 .lock()
3678 .unwrap_or_else(|poisoned| poisoned.into_inner()),
3679 vec![LifecycleEvent::Stopping]
3680 );
3681
3682 handler(ListenerExitPhase::Quiesced(
3683 ListenerExitReason::ListenerFailure,
3684 ));
3685 handler(ListenerExitPhase::Quiesced(
3686 ListenerExitReason::ListenerFailure,
3687 ));
3688 assert!(stop_state.completed.load(Ordering::Acquire));
3689 assert_eq!(
3690 *events
3691 .lock()
3692 .unwrap_or_else(|poisoned| poisoned.into_inner()),
3693 vec![
3694 LifecycleEvent::Stopping,
3695 LifecycleEvent::Stopped {
3696 reason: StopReason::ListenerFailure,
3697 },
3698 ]
3699 );
3700 }
3701
3702 #[test]
3703 fn authentication_precedes_target_validation_and_rejects_duplicates() {
3704 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
3705 let proxy = start_recording_proxy(Arc::clone(&backend));
3706 let valid = proxy.endpoint().authorization_header_value();
3707 let cases = [
3708 "GET not-an-absolute-target HTTP/1.1\r\nHost: mismatch.example\r\n\r\n"
3709 .to_owned(),
3710 "GET not-an-absolute-target HTTP/1.1\r\nHost: mismatch.example\r\nProxy-Authorization: Basic d3Jvbmc6d3Jvbmc=\r\n\r\n"
3711 .to_owned(),
3712 format!(
3713 "GET not-an-absolute-target HTTP/1.1\r\nHost: mismatch.example\r\nProxy-Authorization: {valid}\r\nProxy-Authorization: {valid}\r\n\r\n"
3714 ),
3715 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n\r\n".to_owned(),
3716 ];
3717
3718 for request in cases {
3719 let response = send_raw(&proxy, request.as_bytes());
3720 assert_eq!(response_status(&response), 407, "{request:?}");
3721 let (head, _) = response_parts(&response);
3722 assert!(head.contains(&format!(
3723 "Proxy-Authenticate: Basic realm=\"{}\"",
3724 proxy.endpoint().realm()
3725 )));
3726 }
3727 assert_eq!(backend.request_count(), 0);
3728
3729 let request = format!(
3730 "GET not-an-absolute-target HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3731 auth_header(&proxy)
3732 );
3733 assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 400);
3734 assert_eq!(backend.request_count(), 0);
3735 proxy.stop();
3736 }
3737
3738 #[test]
3739 fn rate_limited_connect_does_not_generate_or_publish_an_identity() {
3740 let limits = ProxyLimits::new(
3741 ProxyLimits::DEFAULT_MAX_HEADER_BYTES,
3742 ProxyLimits::DEFAULT_MAX_BODY_BYTES,
3743 ProxyLimits::DEFAULT_MAX_ACTIVE_CLIENTS,
3744 1,
3745 1,
3746 ProxyLimits::DEFAULT_MAX_TRACKED_HOSTS,
3747 ProxyLimits::DEFAULT_RATE_WINDOW,
3748 )
3749 .unwrap();
3750 let config = ProxyConfig::with_controls(
3751 ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 1),
3752 crate::HostScope::new("welcome").unwrap(),
3753 limits,
3754 ProxyTimeouts::default(),
3755 crate::LoopbackBind::Ipv4,
3756 );
3757 let proxy =
3758 RunningProxy::start(config, Arc::new(UnusedBackend), Arc::new(NoopProxyObserver))
3759 .unwrap();
3760
3761 let admitted = format!(
3762 "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3763 auth_header(&proxy)
3764 );
3765 assert_eq!(response_status(&send_raw(&proxy, admitted.as_bytes())), 500);
3766 assert!(proxy.local_certificate_pin("welcome").is_none());
3767
3768 let limited = format!(
3769 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}\r\n",
3770 auth_header(&proxy)
3771 );
3772 assert_eq!(response_status(&send_raw(&proxy, limited.as_bytes())), 429);
3773 assert!(proxy.local_certificate_pin("welcome").is_none());
3774 proxy.stop();
3775 }
3776
3777 #[test]
3778 fn canonical_forwarding_and_both_header_boundaries_are_enforced() {
3779 let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
3780 headers: vec![
3781 ProxyHeader::new("Content-Type", "text/plain"),
3782 ProxyHeader::new("X-Origin-Keep", "yes"),
3783 ProxyHeader::new("X-HNS-Security-Path", "dane"),
3784 ProxyHeader::new("X-HNS-Future", "private"),
3785 ProxyHeader::new("Connection", "X-Origin-Hop"),
3786 ProxyHeader::new("X-Origin-Hop", "remove"),
3787 ProxyHeader::new("Alt-Svc", "h3=\":443\""),
3788 ProxyHeader::new("Proxy-Authentication-Info", "secret"),
3789 ProxyHeader::new("Proxy-Future", "secret"),
3790 ProxyHeader::new("Content-Length", "999"),
3791 ProxyHeader::new("Transfer-Encoding", "chunked"),
3792 ],
3793 body: b"response-body".to_vec(),
3794 }));
3795 let proxy = start_recording_proxy(Arc::clone(&backend));
3796 let body = b"request-body";
3797 let request = format!(
3798 "POST http://Sub.WELCOME.:8080/private?q=s3cr3t HTTP/1.1\r\nHost: sub.welcome:8080\r\n{}Proxy-Future: secret\r\nX-HNS-Forged: secret\r\nConnection: keep-alive, X-Remove\r\nProxy-Connection: X-Proxy-Remove\r\nX-Remove: secret\r\nX-Proxy-Remove: secret\r\nKeep-Alive: timeout=5\r\nTE: trailers\r\nTrailer: X-Later\r\nAuthorization: Bearer origin-secret\r\nX-Keep: yes\r\nContent-Length: {}\r\n\r\n",
3799 auth_header(&proxy),
3800 body.len()
3801 );
3802 let mut bytes = request.into_bytes();
3803 bytes.extend_from_slice(body);
3804
3805 let response = send_raw(&proxy, &bytes);
3806 assert_eq!(response_status(&response), 200);
3807 let (head, response_body) = response_parts(&response);
3808 assert_eq!(response_body, b"response-body");
3809 let lower_head = head.to_ascii_lowercase();
3810 assert!(lower_head.contains("x-origin-keep: yes\r\n"));
3811 assert!(lower_head.contains("connection: close\r\n"));
3812 assert!(lower_head.contains("content-length: 13\r\n"));
3813 for forbidden in [
3814 "x-hns-",
3815 "x-origin-hop",
3816 "alt-svc",
3817 "proxy-authentication-info",
3818 "proxy-future",
3819 "transfer-encoding",
3820 "content-length: 999",
3821 ] {
3822 assert!(
3823 !lower_head.contains(forbidden),
3824 "leaked {forbidden}: {head}"
3825 );
3826 }
3827
3828 let requests = backend.take_requests();
3829 assert_eq!(requests.len(), 1);
3830 let forwarded = &requests[0];
3831 assert_eq!(forwarded.method, "POST");
3832 assert_eq!(forwarded.scheme, "http");
3833 assert_eq!(forwarded.host, "sub.welcome");
3834 assert_eq!(forwarded.port, 8080);
3835 assert_eq!(forwarded.path_and_query, "/private?q=s3cr3t");
3836 assert_eq!(forwarded.body.as_bytes(), body);
3837 let forwarded_names: Vec<_> = forwarded
3838 .headers
3839 .iter()
3840 .map(|header| header.name.to_ascii_lowercase())
3841 .collect();
3842 assert_eq!(
3843 forwarded_names
3844 .iter()
3845 .filter(|name| *name == "host")
3846 .count(),
3847 1
3848 );
3849 assert!(!forwarded_names.iter().any(|name| {
3850 name.starts_with("proxy-")
3851 || name.starts_with("x-hns-")
3852 || matches!(
3853 name.as_str(),
3854 "connection"
3855 | "keep-alive"
3856 | "te"
3857 | "trailer"
3858 | "transfer-encoding"
3859 | "content-length"
3860 | "x-remove"
3861 | "x-proxy-remove"
3862 )
3863 }));
3864 assert!(forwarded.headers.iter().any(|header| {
3865 header.name.eq_ignore_ascii_case("host") && header.value == "sub.welcome:8080"
3866 }));
3867 assert!(forwarded.headers.iter().any(|header| {
3868 header.name.eq_ignore_ascii_case("authorization")
3869 && header.value == "Bearer origin-secret"
3870 }));
3871 assert!(
3872 forwarded.headers.iter().any(|header| {
3873 header.name.eq_ignore_ascii_case("x-keep") && header.value == "yes"
3874 })
3875 );
3876 proxy.stop();
3877 }
3878
3879 #[test]
3880 fn ordinary_response_delivers_only_sanitized_typed_metadata() {
3881 let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
3882 headers: vec![
3883 ProxyHeader::new("Content-Type", "text/plain"),
3884 ProxyHeader::new("X-HNS-Security-Path", "dane"),
3885 ProxyHeader::new(
3886 "X-HNS-Resolution-Trace",
3887 r#"[{"origin_path":"/private?server=secret"}]"#,
3888 ),
3889 ProxyHeader::new("X-HNS-Future", "untrusted"),
3890 ProxyHeader::new("Connection", "X-HNS-Resolver-Policy"),
3891 ProxyHeader::new("X-HNS-Resolver-Policy", "nominated"),
3892 ],
3893 body: b"response-body-secret".to_vec(),
3894 }));
3895 let observations = Arc::new(Mutex::new(Vec::new()));
3896 let sink = Arc::clone(&observations);
3897 let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
3898 sink.lock()
3899 .unwrap_or_else(|poisoned| poisoned.into_inner())
3900 .push(observation.clone());
3901 });
3902 let proxy = start_recording_proxy_with_metadata(Arc::clone(&backend), observer);
3903 let body = b"request-body-secret";
3904 let request = format!(
3905 "GET http://welcome/private?token=request-secret HTTP/1.1\r\nHost: welcome\r\n{}Sec-Fetch-Dest: document\r\nX-Request-Secret: request-header-secret\r\nContent-Length: {}\r\n\r\n",
3906 auth_header(&proxy),
3907 body.len(),
3908 );
3909 let mut request = request.into_bytes();
3910 request.extend_from_slice(body);
3911
3912 let response = send_raw(&proxy, &request);
3913 assert_eq!(response_status(&response), 200);
3914 let (head, response_body) = response_parts(&response);
3915 assert_eq!(response_body, b"response-body-secret");
3916 assert!(!head.to_ascii_lowercase().contains("x-hns-"));
3917
3918 let observations = observations
3919 .lock()
3920 .unwrap_or_else(|poisoned| poisoned.into_inner());
3921 assert_eq!(observations.len(), 1);
3922 let observation = &observations[0];
3923 assert_eq!(observation.generation(), 1);
3924 assert_eq!(observation.host().as_str(), "welcome");
3925 assert_eq!(observation.method(), ObservedMethod::Get);
3926 assert_eq!(observation.status_code(), 200);
3927 assert!(observation.is_likely_main_frame());
3928 assert_eq!(
3929 observation.metadata().get("X-HNS-Security-Path"),
3930 Some("dane")
3931 );
3932 assert_eq!(
3933 observation.metadata().get("X-HNS-Resolution-Trace"),
3934 Some(r#"[{"origin_path":"/private?server=secret"}]"#)
3935 );
3936 assert_eq!(observation.metadata().get("X-HNS-Future"), None);
3937 assert_eq!(observation.metadata().get("X-HNS-Resolver-Policy"), None);
3938 let diagnostic = format!("{observation:?}");
3939 for secret in [
3940 "private",
3941 "request-secret",
3942 "server=secret",
3943 "request-body-secret",
3944 "request-header-secret",
3945 ] {
3946 assert!(
3947 !diagnostic.contains(secret),
3948 "leaked {secret}: {diagnostic}"
3949 );
3950 }
3951 drop(observations);
3952 proxy.stop();
3953 }
3954
3955 #[test]
3956 fn revocation_at_backend_return_boundary_prevents_response_head_publication() {
3957 let (entered_tx, entered_rx) = mpsc::channel();
3958 let (release_tx, release_rx) = mpsc::channel();
3959 let authority = Arc::new(GatedPublicationAuthority {
3960 revoked: AtomicBool::new(false),
3961 entered: Mutex::new(Some(entered_tx)),
3962 release: Mutex::new(release_rx),
3963 });
3964 let backend = Arc::new(PublicationPermitBackend {
3965 permit: ProxyPublicationPermit::new(authority.clone()),
3966 });
3967 let proxy =
3968 RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
3969 let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
3970 client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3971 let request = format!(
3972 "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3973 auth_header(&proxy)
3974 );
3975 client.write_all(request.as_bytes()).unwrap();
3976 client.flush().unwrap();
3977
3978 entered_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3979 authority.revoked.store(true, AtomicOrdering::Release);
3980 release_tx.send(()).unwrap();
3981
3982 let mut response = Vec::new();
3983 client.read_to_end(&mut response).unwrap();
3984 assert!(
3985 response.is_empty(),
3986 "revoked backend result published bytes: {response:?}"
3987 );
3988 proxy.stop();
3989 }
3990
3991 #[test]
3992 fn revocation_at_backend_return_boundary_prevents_tunnel_head_publication() {
3993 let (entered_tx, entered_rx) = mpsc::channel();
3994 let (release_tx, release_rx) = mpsc::channel();
3995 let authority = Arc::new(GatedPublicationAuthority {
3996 revoked: AtomicBool::new(false),
3997 entered: Mutex::new(Some(entered_tx)),
3998 release: Mutex::new(release_rx),
3999 });
4000 let backend = Arc::new(PublicationPermitTunnelBackend {
4001 permit: ProxyPublicationPermit::new(authority.clone()),
4002 });
4003 let proxy =
4004 RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
4005 let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
4006 client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
4007 let request = format!(
4008 "GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: key\r\nSec-WebSocket-Version: 13\r\n\r\n",
4009 auth_header(&proxy)
4010 );
4011 client.write_all(request.as_bytes()).unwrap();
4012 client.flush().unwrap();
4013
4014 entered_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4015 authority.revoked.store(true, AtomicOrdering::Release);
4016 release_tx.send(()).unwrap();
4017
4018 let mut response = Vec::new();
4019 client.read_to_end(&mut response).unwrap();
4020 assert!(
4021 response.is_empty(),
4022 "revoked tunnel result published bytes: {response:?}"
4023 );
4024 proxy.stop();
4025 }
4026
4027 #[test]
4028 fn iframe_response_status_is_not_marked_as_main_frame() {
4029 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"iframe")));
4030 let observations = Arc::new(Mutex::new(Vec::new()));
4031 let sink = Arc::clone(&observations);
4032 let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4033 sink.lock()
4034 .unwrap_or_else(|poisoned| poisoned.into_inner())
4035 .push(observation.clone());
4036 });
4037 let proxy = start_recording_proxy_with_metadata(backend, observer);
4038 let request = format!(
4039 "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}Sec-Fetch-Dest: iframe\r\nSec-Fetch-Mode: navigate\r\nAccept: text/html\r\n\r\n",
4040 auth_header(&proxy)
4041 );
4042
4043 assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 200);
4044 let observations = observations
4045 .lock()
4046 .unwrap_or_else(|poisoned| poisoned.into_inner());
4047 assert_eq!(observations.len(), 1);
4048 assert_eq!(observations[0].status_code(), 200);
4049 assert!(!observations[0].is_likely_main_frame());
4050 drop(observations);
4051 proxy.stop();
4052 }
4053
4054 #[test]
4055 fn invalid_response_head_reports_only_the_proxy_generated_main_frame_error() {
4056 let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
4057 headers: vec![
4058 ProxyHeader::new("X-HNS-TLS-Policy", "dane"),
4059 ProxyHeader::new("x-hns-tls-policy", "webpki"),
4060 ],
4061 body: Vec::new(),
4062 }));
4063 let observations = Arc::new(Mutex::new(Vec::new()));
4064 let sink = Arc::clone(&observations);
4065 let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4066 sink.lock()
4067 .unwrap_or_else(|poisoned| poisoned.into_inner())
4068 .push(observation.clone());
4069 });
4070 let proxy = start_recording_proxy_with_metadata(backend, observer);
4071 let request = format!(
4072 "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
4073 auth_header(&proxy)
4074 );
4075
4076 assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 502);
4077 let observations = observations
4078 .lock()
4079 .unwrap_or_else(|poisoned| poisoned.into_inner());
4080 assert_eq!(observations.len(), 1);
4081 assert_eq!(observations[0].status_code(), 502);
4082 assert!(observations[0].is_likely_main_frame());
4083 assert!(observations[0].metadata().is_empty());
4084 drop(observations);
4085 proxy.stop();
4086 }
4087
4088 #[test]
4089 fn backend_failure_reports_the_proxy_generated_main_frame_status() {
4090 let observations = Arc::new(Mutex::new(Vec::new()));
4091 let sink = Arc::clone(&observations);
4092 let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4093 sink.lock()
4094 .unwrap_or_else(|poisoned| poisoned.into_inner())
4095 .push(observation.clone());
4096 });
4097 let proxy = RunningProxy::start_with_metadata_observer(
4098 test_config(),
4099 Arc::new(UnusedBackend),
4100 Arc::new(NoopProxyObserver),
4101 observer,
4102 )
4103 .unwrap();
4104 let request = format!(
4105 "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
4106 auth_header(&proxy)
4107 );
4108
4109 assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 500);
4110 let observations = observations
4111 .lock()
4112 .unwrap_or_else(|poisoned| poisoned.into_inner());
4113 assert_eq!(observations.len(), 1);
4114 assert_eq!(observations[0].status_code(), 500);
4115 assert!(observations[0].is_likely_main_frame());
4116 assert!(observations[0].metadata().is_empty());
4117 drop(observations);
4118 proxy.stop();
4119 }
4120
4121 #[test]
4122 fn panicking_metadata_observer_does_not_interrupt_response_delivery() {
4123 let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
4124 headers: vec![ProxyHeader::new("X-HNS-Security-Path", "dane")],
4125 body: b"ok".to_vec(),
4126 }));
4127 let observer = Arc::new(|_observation: &ProxyResponseMetadataObservation| {
4128 panic!("test metadata observer panic");
4129 });
4130 let proxy = start_recording_proxy_with_metadata(Arc::clone(&backend), observer);
4131 let request = format!(
4132 "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
4133 auth_header(&proxy)
4134 );
4135
4136 for _ in 0..2 {
4137 let response = send_raw(&proxy, request.as_bytes());
4138 assert_eq!(response_status(&response), 200);
4139 assert_eq!(response_parts(&response).1, b"ok");
4140 }
4141 assert_eq!(backend.request_count(), 2);
4142 proxy.stop();
4143 }
4144
4145 #[test]
4146 fn fixed_and_chunked_request_bodies_are_forwarded_without_framing_fields() {
4147 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"ok")));
4148 let proxy = start_recording_proxy(Arc::clone(&backend));
4149 let fixed = format!(
4150 "POST http://welcome/fixed HTTP/1.1\r\nHost: welcome\r\n{}Content-Length: 5\r\n\r\nfixed",
4151 auth_header(&proxy)
4152 );
4153 assert_eq!(response_status(&send_raw(&proxy, fixed.as_bytes())), 200);
4154
4155 let chunked = format!(
4156 "POST http://welcome/chunked HTTP/1.1\r\nHost: welcome\r\n{}Transfer-Encoding: chunked\r\n\r\n4\r\nWiki\r\n5\r\npedia\r\n0\r\nX-Origin-Trailer: ignored\r\n\r\n",
4157 auth_header(&proxy)
4158 );
4159 assert_eq!(response_status(&send_raw(&proxy, chunked.as_bytes())), 200);
4160
4161 let requests = backend.take_requests();
4162 assert_eq!(requests.len(), 2);
4163 assert_eq!(requests[0].path_and_query, "/fixed");
4164 assert_eq!(requests[0].body.as_bytes(), b"fixed");
4165 assert_eq!(requests[1].path_and_query, "/chunked");
4166 assert_eq!(requests[1].body.as_bytes(), b"Wikipedia");
4167 for request in requests {
4168 assert!(!request.headers.iter().any(|header| {
4169 header.name.eq_ignore_ascii_case("content-length")
4170 || header.name.eq_ignore_ascii_case("transfer-encoding")
4171 || header.name.eq_ignore_ascii_case("x-hns-trailer")
4172 || header.name.eq_ignore_ascii_case("x-origin-trailer")
4173 }));
4174 }
4175 proxy.stop();
4176 }
4177
4178 #[test]
4179 fn oversized_expect_request_is_rejected_before_continue_or_body_read() {
4180 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4181 let proxy = start_recording_proxy(Arc::clone(&backend));
4182 let request = format!(
4183 "POST http://welcome/upload HTTP/1.1\r\nHost: welcome\r\n{}Expect: 100-continue\r\nContent-Length: {}\r\n\r\n",
4184 auth_header(&proxy),
4185 ProxyLimits::DEFAULT_MAX_BODY_BYTES + 1,
4186 );
4187
4188 let response = send_raw(&proxy, request.as_bytes());
4189 assert_eq!(response_status(&response), 413);
4190 assert!(!response.starts_with(b"HTTP/1.1 100 Continue"));
4191 assert_eq!(backend.request_count(), 0);
4192 proxy.stop();
4193 }
4194
4195 #[test]
4196 fn connect_rejections_before_200_never_publish_a_pin_or_reach_the_backend() {
4197 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4198 let proxy = start_recording_proxy(Arc::clone(&backend));
4199 let valid = proxy.endpoint().authorization_header_value();
4200 let cases = [
4201 (
4202 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n\r\n".to_owned(),
4203 407,
4204 ),
4205 (
4206 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\nProxy-Authorization: Basic d3Jvbmc6d3Jvbmc=\r\n\r\n"
4207 .to_owned(),
4208 407,
4209 ),
4210 (
4211 format!(
4212 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\nProxy-Authorization: {valid}\r\nProxy-Authorization: {valid}\r\n\r\n"
4213 ),
4214 407,
4215 ),
4216 (
4217 format!(
4218 "CONNECT other:443 HTTP/1.1\r\nHost: other:443\r\n{}\r\n",
4219 auth_header(&proxy)
4220 ),
4221 403,
4222 ),
4223 (
4224 format!(
4225 "CONNECT welcome:443 HTTP/1.1\r\nHost: sub.welcome:443\r\n{}\r\n",
4226 auth_header(&proxy)
4227 ),
4228 400,
4229 ),
4230 (
4231 format!(
4232 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}Content-Length: 1\r\n\r\nx",
4233 auth_header(&proxy)
4234 ),
4235 400,
4236 ),
4237 (
4238 format!(
4239 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n",
4240 auth_header(&proxy)
4241 ),
4242 400,
4243 ),
4244 (
4245 format!(
4246 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}Expect: 100-continue\r\nContent-Length: 0\r\n\r\n",
4247 auth_header(&proxy)
4248 ),
4249 417,
4250 ),
4251 ];
4252
4253 for (request, expected_status) in cases {
4254 let response = send_raw(&proxy, request.as_bytes());
4255 assert_eq!(response_status(&response), expected_status, "{request:?}");
4256 assert!(proxy.local_certificate_pin("welcome").is_none());
4257 assert!(proxy.local_certificate_pin("other").is_none());
4258 assert!(!proxy.matches_local_certificate("welcome", b"not-a-certificate"));
4259 }
4260 assert_eq!(backend.request_count(), 0);
4261 proxy.stop();
4262 }
4263
4264 #[test]
4265 fn exact_sni_get_and_post_use_https_routes_and_sanitize_both_header_boundaries() {
4266 let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
4267 headers: vec![
4268 ProxyHeader::new("Content-Type", "text/plain"),
4269 ProxyHeader::new("X-Origin-Keep", "yes"),
4270 ProxyHeader::new("X-HNS-Security-Path", "dane"),
4271 ProxyHeader::new("Alt-Svc", "h3=\":443\""),
4272 ProxyHeader::new("Connection", "X-Origin-Hop"),
4273 ProxyHeader::new("X-Origin-Hop", "remove"),
4274 ],
4275 body: b"tls-ok".to_vec(),
4276 }));
4277 let proxy = start_recording_proxy(Arc::clone(&backend));
4278
4279 let get = b"GET /asset?q=1 HTTP/1.1\r\nHost: welcome\r\nProxy-Authorization: forged\r\nProxy-Future: secret\r\nX-HNS-Forged: secret\r\nConnection: close, X-Remove\r\nX-Remove: secret\r\nAuthorization: Bearer origin-secret\r\nX-Keep: yes\r\n\r\n";
4280 let (get_response, get_certificate) =
4281 send_tls_request(&proxy, "welcome:443", "welcome", get);
4282 assert_eq!(response_status(&get_response), 200);
4283 let (get_head, get_body) = response_parts(&get_response);
4284 assert_eq!(get_body, b"tls-ok");
4285 let lower_get_head = get_head.to_ascii_lowercase();
4286 assert!(lower_get_head.contains("x-origin-keep: yes\r\n"));
4287 for forbidden in ["x-hns-", "alt-svc", "x-origin-hop"] {
4288 assert!(!lower_get_head.contains(forbidden), "{get_head}");
4289 }
4290 assert!(proxy.matches_local_certificate("welcome", &get_certificate));
4291 assert!(!proxy.matches_local_certificate("sub.welcome", &get_certificate));
4292
4293 let post_head = b"POST /submit?q=2 HTTP/1.1\r\nHost: sub.welcome:8443\r\nContent-Type: text/plain\r\nContent-Length: 7\r\nProxy-Future: secret\r\nX-HNS-Forged: secret\r\nX-Keep: post\r\n\r\n";
4294 let mut post = post_head.to_vec();
4295 post.extend_from_slice(b"payload");
4296 let (post_response, post_certificate) =
4297 send_tls_request(&proxy, "sub.welcome:8443", "sub.welcome", &post);
4298 assert_eq!(response_status(&post_response), 200);
4299 assert!(proxy.matches_local_certificate("sub.welcome", &post_certificate));
4300 assert!(!proxy.matches_local_certificate("welcome", &post_certificate));
4301
4302 let requests = backend.take_requests();
4303 assert_eq!(requests.len(), 2);
4304 let get_request = &requests[0];
4305 assert_eq!(get_request.method, "GET");
4306 assert_eq!(get_request.scheme, "https");
4307 assert_eq!(get_request.host, "welcome");
4308 assert_eq!(get_request.port, 443);
4309 assert_eq!(get_request.path_and_query, "/asset?q=1");
4310 assert!(get_request.body.is_empty());
4311 assert!(get_request.headers.iter().any(|header| {
4312 header.name.eq_ignore_ascii_case("host") && header.value == "welcome"
4313 }));
4314 assert!(get_request.headers.iter().any(|header| {
4315 header.name.eq_ignore_ascii_case("authorization")
4316 && header.value == "Bearer origin-secret"
4317 }));
4318
4319 let post_request = &requests[1];
4320 assert_eq!(post_request.method, "POST");
4321 assert_eq!(post_request.scheme, "https");
4322 assert_eq!(post_request.host, "sub.welcome");
4323 assert_eq!(post_request.port, 8443);
4324 assert_eq!(post_request.path_and_query, "/submit?q=2");
4325 assert_eq!(post_request.body.as_bytes(), b"payload");
4326 assert!(post_request.headers.iter().any(|header| {
4327 header.name.eq_ignore_ascii_case("host") && header.value == "sub.welcome:8443"
4328 }));
4329
4330 for request in requests {
4331 assert!(!request.headers.iter().any(|header| {
4332 let name = header.name.to_ascii_lowercase();
4333 name.starts_with("proxy-")
4334 || name.starts_with("x-hns-")
4335 || matches!(
4336 name.as_str(),
4337 "connection" | "content-length" | "transfer-encoding" | "x-remove"
4338 )
4339 }));
4340 }
4341 proxy.stop();
4342 }
4343
4344 #[test]
4345 fn wrong_or_missing_sni_and_h2_only_never_reach_the_backend() {
4346 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4347 let proxy = start_recording_proxy(Arc::clone(&backend));
4348 let cases: [(&str, bool, &[&[u8]]); 3] = [
4349 ("other.welcome", true, &[b"http/1.1"]),
4350 ("welcome", false, &[b"http/1.1"]),
4351 ("welcome", true, &[b"h2"]),
4352 ];
4353
4354 for (server_name, enable_sni, alpn_protocols) in cases {
4355 let stream = begin_authenticated_connect(&proxy, "welcome:443");
4356 assert!(proxy.local_certificate_pin("welcome").is_some());
4357 assert!(
4358 complete_tls_handshake(stream, server_name, enable_sni, alpn_protocols).is_err(),
4359 "unexpectedly accepted SNI={server_name:?}, enabled={enable_sni}, ALPN={alpn_protocols:?}"
4360 );
4361 }
4362 assert_eq!(backend.request_count(), 0);
4363 proxy.stop();
4364 }
4365
4366 #[test]
4367 fn invalid_connected_targets_never_reach_the_backend() {
4368 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4369 let proxy = start_recording_proxy(Arc::clone(&backend));
4370 let cases: [(&str, &str, &[u8], u16); 6] = [
4371 (
4372 "welcome:443",
4373 "welcome",
4374 b"GET / HTTP/1.1\r\nHost: other.welcome\r\n\r\n",
4375 400,
4376 ),
4377 (
4378 "welcome:8443",
4379 "welcome",
4380 b"GET / HTTP/1.1\r\nHost: welcome\r\n\r\n",
4381 400,
4382 ),
4383 (
4384 "welcome:443",
4385 "welcome",
4386 b"GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n\r\n",
4387 400,
4388 ),
4389 (
4390 "welcome:443",
4391 "welcome",
4392 b"CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n\r\n",
4393 400,
4394 ),
4395 (
4396 "welcome:443",
4397 "welcome",
4398 b"GET /socket HTTP/1.1\r\nHost: welcome\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n",
4399 501,
4400 ),
4401 (
4402 "welcome:443",
4403 "welcome",
4404 b"GET wss://welcome/socket HTTP/1.1\r\nHost: welcome\r\n\r\n",
4405 400,
4406 ),
4407 ];
4408
4409 for (authority, server_name, request, expected_status) in cases {
4410 let (response, _certificate) =
4411 send_tls_request(&proxy, authority, server_name, request);
4412 assert_eq!(
4413 response_status(&response),
4414 expected_status,
4415 "authority={authority:?}, request={request:?}"
4416 );
4417 }
4418 assert_eq!(backend.request_count(), 0);
4419 proxy.stop();
4420 }
4421
4422 #[test]
4423 fn certificate_pins_are_generation_bound_and_revoked_on_stop() {
4424 let session = ProxySessionId::generate().unwrap();
4425 let first_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"first")));
4426 let second_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"second")));
4427 let first = RunningProxy::start(
4428 ProxyConfig::new(
4429 ProxyInstanceId::new(session.clone(), 1),
4430 crate::HostScope::new("welcome").unwrap(),
4431 ),
4432 first_backend,
4433 Arc::new(NoopProxyObserver),
4434 )
4435 .unwrap();
4436 let second = RunningProxy::start(
4437 ProxyConfig::new(
4438 ProxyInstanceId::new(session, 2),
4439 crate::HostScope::new("welcome").unwrap(),
4440 ),
4441 second_backend,
4442 Arc::new(NoopProxyObserver),
4443 )
4444 .unwrap();
4445 let request = b"GET / HTTP/1.1\r\nHost: welcome\r\n\r\n";
4446
4447 let (first_response, first_certificate) =
4448 send_tls_request(&first, "welcome:443", "welcome", request);
4449 let (second_response, second_certificate) =
4450 send_tls_request(&second, "welcome:443", "welcome", request);
4451 assert_eq!(response_status(&first_response), 200);
4452 assert_eq!(response_status(&second_response), 200);
4453 let first_pin = first.local_certificate_pin("welcome").unwrap();
4454 let second_pin = second.local_certificate_pin("welcome").unwrap();
4455 assert_eq!(first_pin.instance().generation(), 1);
4456 assert_eq!(second_pin.instance().generation(), 2);
4457 assert_ne!(
4458 first_pin.certificate_sha256(),
4459 second_pin.certificate_sha256()
4460 );
4461 assert!(first.matches_local_certificate("welcome", &first_certificate));
4462 assert!(!first.matches_local_certificate("welcome", &second_certificate));
4463 assert!(second.matches_local_certificate("welcome", &second_certificate));
4464 assert!(!second.matches_local_certificate("welcome", &first_certificate));
4465
4466 first.stop();
4467 assert!(first.local_certificate_pin("welcome").is_none());
4468 assert!(!first.matches_local_certificate("welcome", &first_certificate));
4469 assert!(second.local_certificate_pin("welcome").is_some());
4470 assert!(second.matches_local_certificate("welcome", &second_certificate));
4471 second.stop();
4472 assert!(second.local_certificate_pin("welcome").is_none());
4473 assert!(!second.matches_local_certificate("welcome", &second_certificate));
4474 }
4475
4476 #[test]
4477 fn stop_interrupts_a_partial_client_hello_and_revokes_its_pin() {
4478 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4479 let proxy = start_recording_proxy(Arc::clone(&backend));
4480 let mut stream = begin_authenticated_connect(&proxy, "welcome:443");
4481 assert!(proxy.local_certificate_pin("welcome").is_some());
4482 stream
4483 .write_all(b"\x16\x03\x03\x00\x40\x01\x00\x00")
4484 .unwrap();
4485 stream.flush().unwrap();
4486 wait_for_active_clients(&proxy, 1);
4487
4488 let started = Instant::now();
4489 proxy.stop();
4490 assert!(started.elapsed() < Duration::from_secs(1));
4491 assert!(proxy.is_stopped());
4492 assert_eq!(proxy.active_clients(), 0);
4493 assert_eq!(backend.request_count(), 0);
4494 assert!(proxy.local_certificate_pin("welcome").is_none());
4495 assert!(!proxy.matches_local_certificate("welcome", b"not-a-certificate"));
4496 assert_connection_closed(stream);
4497 }
4498
4499 #[test]
4500 fn stop_joins_tls_clients_stalled_in_inner_headers_and_bodies() {
4501 let header_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4502 let header_proxy = start_recording_proxy(Arc::clone(&header_backend));
4503 let header_stream = begin_authenticated_connect(&header_proxy, "welcome:443");
4504 let (mut header_tls, _certificate) =
4505 complete_tls_handshake(header_stream, "welcome", true, &[b"http/1.1"]).unwrap();
4506 header_tls
4507 .write_all(b"GET / HTTP/1.1\r\nHost: wel")
4508 .unwrap();
4509 header_tls.flush().unwrap();
4510 wait_for_active_clients(&header_proxy, 1);
4511
4512 let started = Instant::now();
4513 header_proxy.stop();
4514 assert!(started.elapsed() < Duration::from_secs(1));
4515 assert_eq!(header_proxy.active_clients(), 0);
4516 assert_eq!(header_backend.request_count(), 0);
4517 assert!(!matches!(header_tls.read(&mut [0_u8; 1]), Ok(1..)));
4518
4519 let body_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4520 let (accepted_tx, accepted_rx) = mpsc::channel();
4521 let body_proxy = RunningProxy::start(
4522 test_config(),
4523 body_backend.clone(),
4524 Arc::new(AcceptedObserver(accepted_tx)),
4525 )
4526 .unwrap();
4527 let body_stream = begin_authenticated_connect(&body_proxy, "welcome:443");
4528 let (mut body_tls, _certificate) =
4529 complete_tls_handshake(body_stream, "welcome", true, &[b"http/1.1"]).unwrap();
4530 body_tls
4531 .write_all(b"POST / HTTP/1.1\r\nHost: welcome\r\nContent-Length: 12\r\n\r\nshort")
4532 .unwrap();
4533 body_tls.flush().unwrap();
4534 accepted_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4535 accepted_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4536
4537 let started = Instant::now();
4538 body_proxy.stop();
4539 assert!(started.elapsed() < Duration::from_secs(1));
4540 assert_eq!(body_proxy.active_clients(), 0);
4541 assert_eq!(body_backend.request_count(), 0);
4542 assert!(!matches!(body_tls.read(&mut [0_u8; 1]), Ok(1..)));
4543 }
4544
4545 #[test]
4546 fn direct_websocket_upgrade_is_sanitized_and_copied_bidirectionally() {
4547 let backend = Arc::new(EchoTunnelBackend::websocket(b"origin"));
4548 let observations = Arc::new(Mutex::new(Vec::new()));
4549 let sink = Arc::clone(&observations);
4550 let (observed_tx, observed_rx) = mpsc::channel();
4551 let metadata_observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4552 sink.lock()
4553 .unwrap_or_else(|poisoned| poisoned.into_inner())
4554 .push(observation.clone());
4555 let _result = observed_tx.send(());
4556 });
4557 let proxy = RunningProxy::start_with_metadata_observer(
4558 test_config(),
4559 backend.clone(),
4560 Arc::new(NoopProxyObserver),
4561 metadata_observer,
4562 )
4563 .unwrap();
4564 let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
4565 client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
4566 client.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
4567 let request = format!(
4568 "GET ws://welcome/socket?q=1 HTTP/1.1\r\nHost: welcome\r\n{}Connection: keep-alive, Upgrade, X-Secret\r\nUpgrade: websocket\r\nSec-WebSocket-Key: key\r\nSec-WebSocket-Version: 13\r\nX-Secret: remove\r\nX-HNS-Client: remove\r\n\r\n",
4569 auth_header(&proxy)
4570 );
4571 client.write_all(request.as_bytes()).unwrap();
4572 client.flush().unwrap();
4573
4574 let response = read_response_head(&mut client).unwrap();
4575 let response_text = std::str::from_utf8(&response).unwrap();
4576 assert_eq!(response_status(&response), 101);
4577 assert!(response_text.contains("Connection: Upgrade\r\n"));
4578 assert!(response_text.contains("Upgrade: websocket\r\n"));
4579 assert!(response_text.contains("Sec-WebSocket-Accept: accepted\r\n"));
4580 assert!(!response_text.contains("keep-alive"));
4581 assert!(!response_text.contains("X-Origin-Hop"));
4582 assert!(!response_text.contains("X-HNS-"));
4583 observed_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4584 {
4585 let observations = observations
4586 .lock()
4587 .unwrap_or_else(|poisoned| poisoned.into_inner());
4588 assert_eq!(observations.len(), 1);
4589 let observation = &observations[0];
4590 assert_eq!(observation.generation(), 1);
4591 assert_eq!(observation.host().as_str(), "welcome");
4592 assert_eq!(observation.method(), ObservedMethod::Get);
4593 assert_eq!(observation.status_code(), 101);
4594 assert!(!observation.is_likely_main_frame());
4595 assert_eq!(
4596 observation.metadata().get("X-HNS-Security-Path"),
4597 Some("secret")
4598 );
4599 let diagnostic = format!("{observation:?}");
4600 assert!(!diagnostic.contains("socket"));
4601 assert!(!diagnostic.contains("q=1"));
4602 assert!(!diagnostic.contains("secret"));
4603 }
4604 let mut from_origin = [0_u8; 6];
4605 client.read_exact(&mut from_origin).unwrap();
4606 assert_eq!(&from_origin, b"origin");
4607 client.write_all(b"client").unwrap();
4608 client.flush().unwrap();
4609 let mut echoed = [0_u8; 6];
4610 client.read_exact(&mut echoed).unwrap();
4611 assert_eq!(&echoed, b"client");
4612 drop(client);
4613
4614 let requests = backend.take_requests();
4615 assert_eq!(requests.len(), 1);
4616 let request = &requests[0];
4617 assert_eq!(request.method, "GET");
4618 assert_eq!(request.scheme, "ws");
4619 assert_eq!(request.host, "welcome");
4620 assert_eq!(request.port, 80);
4621 assert_eq!(request.path_and_query, "/socket?q=1");
4622 assert!(request.body.is_empty());
4623 assert!(request.headers.iter().any(|header| {
4624 header.name.eq_ignore_ascii_case("connection") && header.value == "Upgrade"
4625 }));
4626 assert!(request.headers.iter().any(|header| {
4627 header.name.eq_ignore_ascii_case("upgrade") && header.value == "websocket"
4628 }));
4629 assert!(request.headers.iter().any(|header| {
4630 header.name.eq_ignore_ascii_case("sec-websocket-key") && header.value == "key"
4631 }));
4632 assert!(!request.headers.iter().any(|header| {
4633 header.name.eq_ignore_ascii_case("proxy-authorization")
4634 || header.name.eq_ignore_ascii_case("x-secret")
4635 || header.name.to_ascii_lowercase().starts_with("x-hns-")
4636 }));
4637 proxy.stop();
4638 }
4639
4640 #[test]
4641 fn connect_webpki_passthrough_skips_local_tls_and_copies_bidirectionally() {
4642 let backend = Arc::new(ConnectPassthroughBackend {
4643 requests: Mutex::new(Vec::new()),
4644 });
4645 let observations = Arc::new(Mutex::new(Vec::new()));
4646 let sink = Arc::clone(&observations);
4647 let proxy = RunningProxy::start_with_metadata_observer(
4648 test_config(),
4649 backend.clone(),
4650 Arc::new(NoopProxyObserver),
4651 Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4652 sink.lock()
4653 .unwrap_or_else(|poisoned| poisoned.into_inner())
4654 .push((
4655 observation.kind(),
4656 observation.port(),
4657 observation.correlation_epoch(),
4658 observation.observation_id(),
4659 ));
4660 }),
4661 )
4662 .unwrap();
4663
4664 let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4665 assert!(proxy.local_certificate_pin("welcome").is_none());
4666 let mut from_origin = [0_u8; 6];
4667 client.read_exact(&mut from_origin).unwrap();
4668 assert_eq!(&from_origin, b"origin");
4669 client.write_all(b"client").unwrap();
4670 client.flush().unwrap();
4671 let mut echoed = [0_u8; 6];
4672 client.read_exact(&mut echoed).unwrap();
4673 assert_eq!(&echoed, b"client");
4674 drop(client);
4675
4676 let requests = backend
4677 .requests
4678 .lock()
4679 .unwrap_or_else(|poisoned| poisoned.into_inner());
4680 assert_eq!(requests.len(), 1);
4681 assert_eq!(requests[0].method, "CONNECT");
4682 assert_eq!(requests[0].scheme, "https");
4683 assert_eq!(requests[0].host, "welcome");
4684 assert_eq!(requests[0].port, 443);
4685 assert_eq!(
4686 *observations
4687 .lock()
4688 .unwrap_or_else(|poisoned| poisoned.into_inner()),
4689 vec![(
4690 ProxyMetadataObservationKind::WebPkiConnectDecision,
4691 Some(443),
4692 Some(9),
4693 Some(17),
4694 )]
4695 );
4696 drop(requests);
4697 proxy.stop();
4698 }
4699
4700 #[test]
4701 fn connect_download_is_not_serialized_behind_idle_client_upload_polls() {
4702 const DOWNLOAD_BYTES: usize = 4 * 1024 * 1024;
4703
4704 let backend = Arc::new(BulkDownloadBackend {
4705 bytes: vec![0x5a; DOWNLOAD_BYTES],
4706 });
4707 let proxy =
4708 RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
4709 let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4710 client
4711 .set_read_timeout(Some(Duration::from_secs(5)))
4712 .unwrap();
4713 let started = Instant::now();
4714 let mut downloaded = Vec::new();
4715 client.read_to_end(&mut downloaded).unwrap();
4716
4717 assert_eq!(downloaded.len(), DOWNLOAD_BYTES);
4718 assert!(downloaded.iter().all(|byte| *byte == 0x5a));
4719 assert!(started.elapsed() < Duration::from_secs(3));
4722 proxy.stop();
4723 }
4724
4725 #[test]
4726 fn connect_upload_is_not_serialized_behind_idle_origin_download_polls() {
4727 const UPLOAD_BYTES: usize = 4 * 1024 * 1024;
4728
4729 let state = Arc::new(BulkUploadState {
4730 received: std::sync::atomic::AtomicUsize::new(0),
4731 shutdown: AtomicBool::new(false),
4732 });
4733 let proxy = RunningProxy::start(
4734 test_config(),
4735 Arc::new(BulkUploadBackend {
4736 state: Arc::clone(&state),
4737 }),
4738 Arc::new(NoopProxyObserver),
4739 )
4740 .unwrap();
4741 let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4742 client
4743 .set_write_timeout(Some(Duration::from_secs(5)))
4744 .unwrap();
4745 let started = Instant::now();
4746 client.write_all(&vec![0x6b; UPLOAD_BYTES]).unwrap();
4747 client.flush().unwrap();
4748 let deadline = started + Duration::from_secs(5);
4749 while state.received.load(AtomicOrdering::Acquire) != UPLOAD_BYTES
4750 && Instant::now() < deadline
4751 {
4752 thread::yield_now();
4753 }
4754
4755 assert_eq!(state.received.load(AtomicOrdering::Acquire), UPLOAD_BYTES);
4756 assert!(started.elapsed() < Duration::from_secs(3));
4757 drop(client);
4758 proxy.stop();
4759 }
4760
4761 #[test]
4762 fn selected_webpki_connect_failure_never_prepares_a_local_identity() {
4763 let proxy = RunningProxy::start(
4764 test_config(),
4765 Arc::new(WebPkiUnavailableBackend),
4766 Arc::new(NoopProxyObserver),
4767 )
4768 .unwrap();
4769 let request = format!(
4770 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}\r\n",
4771 auth_header(&proxy)
4772 );
4773 let response = send_raw(&proxy, request.as_bytes());
4774
4775 assert_eq!(response_status(&response), 502);
4776 assert!(response.starts_with(b"HTTP/1.1 502 ICANN WebPKI Origin Unavailable\r\n"));
4777 assert!(proxy.local_certificate_pin("welcome").is_none());
4778 proxy.stop();
4779 }
4780
4781 #[test]
4782 fn unexpected_connect_backend_failures_never_fall_back_to_local_tls() {
4783 for panic in [false, true] {
4784 let proxy = RunningProxy::start(
4785 test_config(),
4786 Arc::new(UnexpectedConnectFailureBackend { panic }),
4787 Arc::new(NoopProxyObserver),
4788 )
4789 .unwrap();
4790 let request = format!(
4791 "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}\r\n",
4792 auth_header(&proxy)
4793 );
4794 let response = send_raw(&proxy, request.as_bytes());
4795
4796 assert_eq!(response_status(&response), 503);
4797 assert!(response.starts_with(b"HTTP/1.1 503 Browser Security Runtime Unavailable\r\n"));
4798 assert!(proxy.local_certificate_pin("welcome").is_none());
4799 proxy.stop();
4800 }
4801 }
4802
4803 #[test]
4804 fn stop_joins_an_idle_webpki_passthrough_without_detached_copy_workers() {
4805 let backend = Arc::new(ConnectPassthroughBackend {
4806 requests: Mutex::new(Vec::new()),
4807 });
4808 let proxy =
4809 RunningProxy::start(test_config(), backend.clone(), Arc::new(NoopProxyObserver))
4810 .unwrap();
4811 let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4812 let mut initial = [0_u8; 6];
4813 client.read_exact(&mut initial).unwrap();
4814 assert_eq!(&initial, b"origin");
4815 wait_for_active_clients(&proxy, 1);
4816
4817 let started = Instant::now();
4818 proxy.stop();
4819 assert!(started.elapsed() < Duration::from_secs(1));
4820 assert!(proxy.is_stopped());
4821 assert_eq!(proxy.active_clients(), 0);
4822 assert_eq!(
4823 backend
4824 .requests
4825 .lock()
4826 .unwrap_or_else(|poisoned| poisoned.into_inner())
4827 .len(),
4828 1
4829 );
4830 assert_connection_closed(client);
4831 }
4832
4833 #[test]
4834 fn post_connect_wss_upgrade_uses_the_same_rust_tunnel_path() {
4835 let backend = Arc::new(EchoTunnelBackend::websocket(Vec::new()));
4836 let proxy =
4837 RunningProxy::start(test_config(), backend.clone(), Arc::new(NoopProxyObserver))
4838 .unwrap();
4839 let stream = begin_authenticated_connect(&proxy, "welcome:443");
4840 let (mut tls, _certificate) =
4841 complete_tls_handshake(stream, "welcome", true, &[b"http/1.1"]).unwrap();
4842 tls.write_all(
4843 b"GET wss://welcome/socket HTTP/1.1\r\nHost: welcome\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: key\r\nSec-WebSocket-Version: 13\r\n\r\nping",
4844 )
4845 .unwrap();
4846 tls.flush().unwrap();
4847
4848 let response = read_response_head(&mut tls).unwrap();
4849 assert_eq!(response_status(&response), 101);
4850 assert!(
4851 !std::str::from_utf8(&response)
4852 .unwrap()
4853 .contains("X-HNS-Security-Path")
4854 );
4855 let mut echoed = [0_u8; 4];
4856 tls.read_exact(&mut echoed).unwrap();
4857 assert_eq!(&echoed, b"ping");
4858 drop(tls);
4859
4860 let requests = backend.take_requests();
4861 assert_eq!(requests.len(), 1);
4862 assert_eq!(requests[0].scheme, "wss");
4863 assert_eq!(requests[0].host, "welcome");
4864 assert_eq!(requests[0].port, 443);
4865 assert_eq!(requests[0].path_and_query, "/socket");
4866 proxy.stop();
4867 }
4868
4869 #[test]
4870 fn invalid_upgrade_response_fails_before_switching_protocols() {
4871 let backend = Arc::new(EchoTunnelBackend::with_head(ProxyResponseHead {
4872 status_code: 200,
4873 reason_phrase: "OK".to_owned(),
4874 headers: vec![
4875 ProxyHeader::new("Connection", "Upgrade"),
4876 ProxyHeader::new("Upgrade", "websocket"),
4877 ProxyHeader::new("X-HNS-Security-Path", "must-not-observe"),
4878 ],
4879 observation_id: None,
4880 }));
4881 let observations = Arc::new(Mutex::new(Vec::new()));
4882 let sink = Arc::clone(&observations);
4883 let metadata_observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4884 sink.lock()
4885 .unwrap_or_else(|poisoned| poisoned.into_inner())
4886 .push(observation.clone());
4887 });
4888 let proxy = RunningProxy::start_with_metadata_observer(
4889 test_config(),
4890 backend.clone(),
4891 Arc::new(NoopProxyObserver),
4892 metadata_observer,
4893 )
4894 .unwrap();
4895 let request = format!(
4896 "GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\n\r\n",
4897 auth_header(&proxy)
4898 );
4899
4900 let response = send_raw(&proxy, request.as_bytes());
4901 assert_eq!(response_status(&response), 502);
4902 assert!(!String::from_utf8_lossy(&response).contains("101 Switching Protocols"));
4903 assert_eq!(backend.take_requests().len(), 1);
4904 assert!(
4905 observations
4906 .lock()
4907 .unwrap_or_else(|poisoned| poisoned.into_inner())
4908 .is_empty()
4909 );
4910 proxy.stop();
4911 }
4912
4913 #[test]
4914 fn upgrade_backend_can_fail_with_a_bounded_http_response() {
4915 let observations = Arc::new(Mutex::new(Vec::new()));
4916 let sink = Arc::clone(&observations);
4917 let metadata_observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4918 sink.lock()
4919 .unwrap_or_else(|poisoned| poisoned.into_inner())
4920 .push(observation.clone());
4921 });
4922 let proxy = RunningProxy::start_with_metadata_observer(
4923 test_config(),
4924 Arc::new(HttpRejectingTunnelBackend),
4925 Arc::new(NoopProxyObserver),
4926 metadata_observer,
4927 )
4928 .unwrap();
4929 let request = format!(
4930 "GET ws://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\nSec-Fetch-Dest: document\r\n\r\n",
4931 auth_header(&proxy)
4932 );
4933
4934 let response = send_raw(&proxy, request.as_bytes());
4935 assert_eq!(response_status(&response), 404);
4936 let (head, body) = response_parts(&response);
4937 assert!(head.contains("Content-Type: text/plain\r\n"));
4938 assert!(head.contains("Content-Length: 22\r\n"));
4939 assert!(!head.contains("Upgrade:"));
4940 assert!(!head.contains("X-HNS-"));
4941 assert_eq!(body, b"verified non-inclusion");
4942 let observations = observations
4943 .lock()
4944 .unwrap_or_else(|poisoned| poisoned.into_inner());
4945 assert_eq!(observations.len(), 1);
4946 assert_eq!(observations[0].status_code(), 404);
4947 assert_eq!(observations[0].method(), ObservedMethod::Get);
4948 assert!(!observations[0].is_likely_main_frame());
4949 assert_eq!(
4950 observations[0].metadata().get("X-HNS-Security-Path"),
4951 Some("verified-non-inclusion")
4952 );
4953 drop(observations);
4954 proxy.stop();
4955 }
4956
4957 #[test]
4958 fn stop_joins_an_idle_upgrade_without_detached_copy_workers() {
4959 let backend = Arc::new(EchoTunnelBackend::websocket(Vec::new()));
4960 let proxy =
4961 RunningProxy::start(test_config(), backend.clone(), Arc::new(NoopProxyObserver))
4962 .unwrap();
4963 let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
4964 client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
4965 let request = format!(
4966 "GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\n\r\n",
4967 auth_header(&proxy)
4968 );
4969 client.write_all(request.as_bytes()).unwrap();
4970 client.flush().unwrap();
4971 assert_eq!(
4972 response_status(&read_response_head(&mut client).unwrap()),
4973 101
4974 );
4975 wait_for_active_clients(&proxy, 1);
4976
4977 let started = Instant::now();
4978 proxy.stop();
4979 assert!(started.elapsed() < Duration::from_secs(1));
4980 assert!(proxy.is_stopped());
4981 assert_eq!(proxy.active_clients(), 0);
4982 assert_eq!(backend.take_requests().len(), 1);
4983 assert_connection_closed(client);
4984 }
4985
4986 #[test]
4987 fn upgrades_and_out_of_scope_targets_never_reach_the_backend() {
4988 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4989 let proxy = start_recording_proxy(Arc::clone(&backend));
4990 let auth = auth_header(&proxy);
4991 let cases = [
4992 (
4993 format!("CONNECT other:443 HTTP/1.1\r\nHost: other:443\r\n{auth}\r\n"),
4994 403,
4995 ),
4996 (
4997 format!(
4998 "GET http://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{auth}Connection: Upgrade\r\nUpgrade: websocket\r\n\r\n"
4999 ),
5000 501,
5001 ),
5002 (
5003 format!("GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{auth}\r\n"),
5004 400,
5005 ),
5006 (
5007 format!("GET http://other/ HTTP/1.1\r\nHost: other\r\n{auth}\r\n"),
5008 403,
5009 ),
5010 (
5011 format!("GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n{auth}\r\n"),
5012 403,
5013 ),
5014 ];
5015
5016 for (request, expected) in cases {
5017 assert_eq!(
5018 response_status(&send_raw(&proxy, request.as_bytes())),
5019 expected,
5020 "{request:?}"
5021 );
5022 }
5023 assert_eq!(backend.request_count(), 0);
5024 proxy.stop();
5025 }
5026
5027 #[test]
5028 fn premature_response_stream_eof_closes_without_a_second_response() {
5029 let backend = Arc::new(RecordingBackend::new(ResponsePlan::ShortStream {
5030 expected_len: 9,
5031 bytes: b"tiny".to_vec(),
5032 }));
5033 let proxy = start_recording_proxy(Arc::clone(&backend));
5034 let request = format!(
5035 "GET http://welcome/stream HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
5036 auth_header(&proxy)
5037 );
5038
5039 let response = send_raw(&proxy, request.as_bytes());
5040 assert_eq!(response_status(&response), 200);
5041 let (head, body) = response_parts(&response);
5042 assert!(head.contains("Content-Length: 9\r\n"));
5043 assert_eq!(body, b"tiny");
5044 assert_eq!(
5045 response
5046 .windows(b"HTTP/1.1".len())
5047 .filter(|window| *window == b"HTTP/1.1")
5048 .count(),
5049 1
5050 );
5051 assert_eq!(backend.request_count(), 1);
5052 proxy.stop();
5053 }
5054
5055 #[test]
5056 fn stop_closes_clients_stalled_in_partial_headers_and_bodies() {
5057 let header_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
5058 let header_proxy = start_recording_proxy(Arc::clone(&header_backend));
5059 let mut partial_head = TcpStream::connect(header_proxy.endpoint().address()).unwrap();
5060 partial_head
5061 .write_all(b"GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n")
5062 .unwrap();
5063 wait_for_active_clients(&header_proxy, 1);
5064 header_proxy.stop();
5065 assert!(header_proxy.is_stopped());
5066 assert_eq!(header_proxy.active_clients(), 0);
5067 assert_eq!(header_backend.request_count(), 0);
5068 assert_connection_closed(partial_head);
5069
5070 let body_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
5071 let (accepted_tx, accepted_rx) = mpsc::channel();
5072 let body_proxy = RunningProxy::start(
5073 test_config(),
5074 body_backend.clone(),
5075 Arc::new(AcceptedObserver(accepted_tx)),
5076 )
5077 .unwrap();
5078 let mut partial_body = TcpStream::connect(body_proxy.endpoint().address()).unwrap();
5079 let request = format!(
5080 "POST http://welcome/upload HTTP/1.1\r\nHost: welcome\r\n{}Content-Length: 10\r\n\r\nabc",
5081 auth_header(&body_proxy)
5082 );
5083 partial_body.write_all(request.as_bytes()).unwrap();
5084 accepted_rx.recv_timeout(TEST_TIMEOUT).unwrap();
5085 wait_for_active_clients(&body_proxy, 1);
5086 body_proxy.stop();
5087 assert!(body_proxy.is_stopped());
5088 assert_eq!(body_proxy.active_clients(), 0);
5089 assert_eq!(body_backend.request_count(), 0);
5090 assert_connection_closed(partial_body);
5091 }
5092
5093 #[test]
5094 fn stop_joins_a_cooperative_backend_response_stream_read() {
5095 let (read_started_tx, read_started_rx) = mpsc::channel();
5096 let backend = Arc::new(CooperativeStreamBackend {
5097 read_started: Mutex::new(Some(read_started_tx)),
5098 });
5099 let proxy =
5100 RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
5101 let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
5102 client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
5103 client.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
5104 let request = format!(
5105 "GET http://welcome/stream HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
5106 auth_header(&proxy)
5107 );
5108 client.write_all(request.as_bytes()).unwrap();
5109 client.flush().unwrap();
5110 read_started_rx.recv_timeout(TEST_TIMEOUT).unwrap();
5111 let response_head = read_response_head(&mut client).unwrap();
5112 assert_eq!(response_status(&response_head), 200);
5113 wait_for_active_clients(&proxy, 1);
5114
5115 let started = Instant::now();
5116 proxy.stop();
5117
5118 assert!(started.elapsed() < Duration::from_secs(1));
5119 assert!(proxy.is_stopped());
5120 assert_eq!(proxy.active_clients(), 0);
5121 assert_connection_closed(client);
5122 }
5123
5124 #[test]
5125 fn helper_rounds_retry_after_up_and_maps_backend_failures() {
5126 assert_eq!(retry_after_seconds(Duration::from_nanos(1)), 1);
5127 assert_eq!(retry_after_seconds(Duration::from_secs(2)), 2);
5128 assert_eq!(
5129 backend_error_status(BackendError::TlsValidationFailed),
5130 (502, "HNS TLS Validation Failed")
5131 );
5132 }
5133
5134 #[test]
5135 fn exact_stream_copy_rejects_premature_eof() {
5136 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
5137 let address = listener.local_addr().unwrap();
5138 let client = std::thread::spawn(move || TcpStream::connect(address).unwrap());
5139 let (mut server, _) = listener.accept().unwrap();
5140 let _client = client.join().unwrap();
5141
5142 assert_eq!(
5143 copy_exact_response(
5144 &mut server,
5145 &mut Cursor::new(b"x".to_vec()),
5146 2,
5147 &CancellationToken::new(),
5148 ),
5149 Err(WriteBackendError::InvalidAfterHead)
5150 );
5151 }
5152 #[test]
5153 fn dane_browser_routes_hns_and_icann_through_one_backend_boundary() {
5154 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain("native")));
5155 let proxy = RunningProxy::start(
5156 dane_browser_config(),
5157 backend.clone(),
5158 Arc::new(NoopProxyObserver),
5159 )
5160 .unwrap();
5161
5162 for host in ["welcome", "sub.unregistered-hns-root", "example.com"] {
5163 let request = format!(
5164 "GET http://{host}/ HTTP/1.1\r\nHost: {host}\r\n{}\r\n",
5165 auth_header(&proxy)
5166 );
5167 assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 200);
5168 }
5169
5170 for host in ["localhost", "example.onion", "127.0.0.1"] {
5171 let request = format!(
5172 "GET http://{host}/ HTTP/1.1\r\nHost: {host}\r\n{}\r\n",
5173 auth_header(&proxy)
5174 );
5175 assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 403);
5176 }
5177 assert_eq!(backend.request_count(), 3);
5178 }
5179
5180 #[test]
5181 fn dane_browser_rejects_named_icann_unsafe_connect_port_before_tls_and_backend() {
5182 let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain("unreachable")));
5183 let proxy = RunningProxy::start(
5184 dane_browser_config(),
5185 backend.clone(),
5186 Arc::new(NoopProxyObserver),
5187 )
5188 .unwrap();
5189 let request = format!(
5190 "CONNECT example.com:22 HTTP/1.1\r\nHost: example.com:22\r\n{}\r\n",
5191 auth_header(&proxy)
5192 );
5193
5194 assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 403);
5195 assert_eq!(backend.request_count(), 0);
5196 }
5197 #[test]
5198 fn authentication_challenge_matching_is_revoked_on_stop_request() {
5199 let proxy = RunningProxy::start(
5200 test_config(),
5201 Arc::new(UnusedBackend),
5202 Arc::new(NoopProxyObserver),
5203 )
5204 .unwrap();
5205 assert!(proxy.matches_authentication_challenge(
5206 "127.0.0.1",
5207 proxy.endpoint().port(),
5208 proxy.endpoint().realm(),
5209 ));
5210 assert!(!proxy.matches_authentication_challenge(
5211 "localhost",
5212 proxy.endpoint().port(),
5213 proxy.endpoint().realm(),
5214 ));
5215 proxy.request_stop();
5216 assert!(proxy.is_stop_requested());
5217 assert!(!proxy.matches_authentication_challenge(
5218 "127.0.0.1",
5219 proxy.endpoint().port(),
5220 proxy.endpoint().realm(),
5221 ));
5222 proxy.stop();
5223 }
5224}