1use crate::accept::{AcceptedSession, PendingHttpForward, PendingTunnel, PendingUdpAssociate};
2use crate::error::SessionOpenError;
3use crate::reply;
4use crate::ConnectionConfig;
5use eggress_core::chain::{ChainExecutor, HopHandler};
6use eggress_core::connector::DirectConnector;
7use eggress_core::relay::relay;
8use eggress_core::BoxStream;
9use eggress_core::{TargetAddr, TargetHost};
10use eggress_routing::{RouteRequest, SelectedRoute};
11use std::pin::Pin;
12use std::task::{Context, Poll};
13use tokio::io::{AsyncReadExt, AsyncWriteExt};
14
15pub struct SessionReport {
16 pub protocol: Option<String>,
17 pub target: Option<String>,
18 pub route: String,
19 pub bytes_upstream: u64,
20 pub bytes_downstream: u64,
21 pub outcome: SessionOutcome,
22 pub failure: Option<FailureCategory>,
23 pub rule_id: Option<String>,
24 pub upstream_group: Option<String>,
25 pub upstream_id: Option<String>,
26 pub selection_reason: Option<eggress_routing::SelectionReason>,
27}
28
29#[derive(Debug)]
31pub enum SessionOutcome {
32 Completed,
33 ClientProtocolError,
34 AuthenticationFailed,
35 HandshakeTimedOut,
36 RouteFailed,
37 RelayFailed,
38 Cancelled,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum FailureCategory {
44 Protocol,
45 Authentication,
46 HandshakeTimeout,
47 Dns,
48 ConnectionRefused,
49 NetworkUnreachable,
50 HostUnreachable,
51 RouteTimeout,
52 RouteHop,
53 UpstreamAuthentication,
54 PolicyDenied,
55 Relay,
56 Cancelled,
57 Internal,
58}
59
60impl SessionReport {
61 pub fn open_failed(
62 error: SessionOpenError,
63 protocol: Option<String>,
64 target: Option<String>,
65 route: String,
66 ) -> Self {
67 SessionReport {
68 protocol,
69 target,
70 route,
71 bytes_upstream: 0,
72 bytes_downstream: 0,
73 outcome: SessionOutcome::RouteFailed,
74 failure: Some(FailureCategory::from(&error)),
75 rule_id: None,
76 upstream_group: None,
77 upstream_id: None,
78 selection_reason: None,
79 }
80 }
81
82 pub fn completed(
83 protocol: Option<String>,
84 target: Option<String>,
85 route: String,
86 bytes_upstream: u64,
87 bytes_downstream: u64,
88 ) -> Self {
89 SessionReport {
90 protocol,
91 target,
92 route,
93 bytes_upstream,
94 bytes_downstream,
95 outcome: SessionOutcome::Completed,
96 failure: None,
97 rule_id: None,
98 upstream_group: None,
99 upstream_id: None,
100 selection_reason: None,
101 }
102 }
103
104 pub fn cancelled(protocol: Option<String>, target: Option<String>, route: String) -> Self {
105 SessionReport {
106 protocol,
107 target,
108 route,
109 bytes_upstream: 0,
110 bytes_downstream: 0,
111 outcome: SessionOutcome::Cancelled,
112 failure: Some(FailureCategory::Cancelled),
113 rule_id: None,
114 upstream_group: None,
115 upstream_id: None,
116 selection_reason: None,
117 }
118 }
119
120 pub fn rejected(protocol: Option<String>, target: Option<String>, rule_id: String) -> Self {
121 SessionReport {
122 protocol,
123 target,
124 route: "reject".to_string(),
125 bytes_upstream: 0,
126 bytes_downstream: 0,
127 outcome: SessionOutcome::RouteFailed,
128 failure: Some(FailureCategory::PolicyDenied),
129 rule_id: Some(rule_id),
130 upstream_group: None,
131 upstream_id: None,
132 selection_reason: None,
133 }
134 }
135}
136
137impl From<&SessionOpenError> for FailureCategory {
138 fn from(error: &SessionOpenError) -> Self {
139 match error {
140 SessionOpenError::Dns => FailureCategory::Dns,
141 SessionOpenError::Refused => FailureCategory::ConnectionRefused,
142 SessionOpenError::NetworkUnreachable => FailureCategory::NetworkUnreachable,
143 SessionOpenError::HostUnreachable => FailureCategory::HostUnreachable,
144 SessionOpenError::Timeout => FailureCategory::RouteTimeout,
145 SessionOpenError::UpstreamAuthentication => FailureCategory::UpstreamAuthentication,
146 SessionOpenError::Hop { .. } => FailureCategory::RouteHop,
147 SessionOpenError::PolicyDenied => FailureCategory::PolicyDenied,
148 SessionOpenError::Other(_) => FailureCategory::Relay,
149 }
150 }
151}
152
153impl FailureCategory {
154 pub fn from_io_error(error: &std::io::Error) -> Self {
155 match error.kind() {
156 std::io::ErrorKind::ConnectionRefused => FailureCategory::ConnectionRefused,
157 std::io::ErrorKind::ConnectionReset => FailureCategory::Relay,
158 std::io::ErrorKind::TimedOut => FailureCategory::Relay,
159 _ => FailureCategory::Relay,
160 }
161 }
162}
163
164pub async fn execute(session: AcceptedSession, config: &ConnectionConfig) -> SessionReport {
166 match session {
167 AcceptedSession::Tunnel(pending) => {
168 let protocol = Some(match pending.protocol {
169 crate::accept::TunnelProtocol::HttpConnect => "http".to_string(),
170 crate::accept::TunnelProtocol::Http2 => "h2".to_string(),
171 crate::accept::TunnelProtocol::Http3 => "h3".to_string(),
172 crate::accept::TunnelProtocol::WebSocket => "websocket".to_string(),
173 crate::accept::TunnelProtocol::Socks4 => "socks4".to_string(),
174 crate::accept::TunnelProtocol::Socks5 => "socks5".to_string(),
175 crate::accept::TunnelProtocol::Shadowsocks => "shadowsocks".to_string(),
176 crate::accept::TunnelProtocol::ShadowsocksR => "ssr".to_string(),
177 crate::accept::TunnelProtocol::Trojan => "trojan".to_string(),
178 crate::accept::TunnelProtocol::Raw => "raw".to_string(),
179 });
180 let target = Some(pending.target.to_string());
181 execute_tunnel(pending, config, protocol, target).await
182 }
183 AcceptedSession::HttpForward(pending) => {
184 let target = Some(pending.target.to_string());
185 execute_http_forward(pending, config, target).await
186 }
187 AcceptedSession::UdpAssociate(pending) => execute_udp_associate(pending, config).await,
188 AcceptedSession::Echo(stream) => execute_echo(stream).await,
189 }
190}
191
192async fn execute_echo(mut stream: BoxStream) -> SessionReport {
193 let mut buf = [0u8; 16 * 1024];
194 let mut bytes = 0u64;
195 loop {
196 match stream.read(&mut buf).await {
197 Ok(0) => break,
198 Ok(n) => {
199 bytes += n as u64;
200 if stream.write_all(&buf[..n]).await.is_err() {
201 break;
202 }
203 }
204 Err(_) => break,
205 }
206 }
207 SessionReport::completed(
208 Some("echo".to_string()),
209 None,
210 "echo".to_string(),
211 bytes,
212 bytes,
213 )
214}
215
216fn route_description(selected: &SelectedRoute) -> String {
217 match selected {
218 SelectedRoute::Direct {
219 selection_reason, ..
220 } => match selection_reason {
221 eggress_routing::SelectionReason::DirectFallback => "direct(fallback)".to_string(),
222 _ => "direct".to_string(),
223 },
224 SelectedRoute::Upstream {
225 upstream, group, ..
226 } => format!("upstream({}/{})", group.0, upstream),
227 }
228}
229
230fn route_metadata(
231 selected: &SelectedRoute,
232) -> (
233 Option<String>,
234 Option<String>,
235 Option<String>,
236 Option<eggress_routing::SelectionReason>,
237) {
238 match selected {
239 SelectedRoute::Direct {
240 decision,
241 selection_reason,
242 } => {
243 let rule_id = match decision {
244 eggress_routing::RouteDecision::Direct { rule, .. }
245 | eggress_routing::RouteDecision::UpstreamGroup { rule, .. }
246 | eggress_routing::RouteDecision::Reject { rule, .. } => rule.0.to_string(),
247 };
248 (Some(rule_id), None, None, Some(*selection_reason))
249 }
250 SelectedRoute::Upstream {
251 decision,
252 group,
253 upstream,
254 selection_reason,
255 ..
256 } => {
257 let rule_id = match decision {
258 eggress_routing::RouteDecision::Direct { rule, .. }
259 | eggress_routing::RouteDecision::UpstreamGroup { rule, .. }
260 | eggress_routing::RouteDecision::Reject { rule, .. } => rule.0.to_string(),
261 };
262 (
263 Some(rule_id),
264 Some(group.0.to_string()),
265 Some(upstream.to_string()),
266 Some(*selection_reason),
267 )
268 }
269 }
270}
271
272struct OpenedRoute {
273 stream: BoxStream,
274 active_lease: Option<eggress_routing::lease::ActiveLease>,
275 route_description: String,
276 rule_id: Option<String>,
277 upstream_group: Option<String>,
278 upstream_id: Option<String>,
279 selection_reason: Option<eggress_routing::SelectionReason>,
280}
281
282fn upstream_protocol_label(chain: &eggress_uri::ProxyChainSpec) -> &'static str {
283 chain
284 .hops
285 .first()
286 .and_then(|h| h.protocols.first())
287 .map(|p| match p {
288 eggress_uri::ProtocolSpec::Http => "http",
289 eggress_uri::ProtocolSpec::HttpOnly => "httponly",
290 eggress_uri::ProtocolSpec::Socks4 => "socks4",
291 eggress_uri::ProtocolSpec::Socks5 => "socks5",
292 eggress_uri::ProtocolSpec::Shadowsocks => "shadowsocks",
293 eggress_uri::ProtocolSpec::ShadowsocksR => "ssr",
294 eggress_uri::ProtocolSpec::Trojan => "trojan",
295 eggress_uri::ProtocolSpec::Http2 => "h2",
296 eggress_uri::ProtocolSpec::Http3 => "h3",
297 eggress_uri::ProtocolSpec::Quic => "quic",
298 eggress_uri::ProtocolSpec::WebSocket => "websocket",
299 eggress_uri::ProtocolSpec::Raw => "raw",
300 eggress_uri::ProtocolSpec::Ssh => "ssh",
301 eggress_uri::ProtocolSpec::Unix => "unix",
302 })
303 .unwrap_or("unknown")
304}
305
306fn failure_reason_label(error: &SessionOpenError) -> &'static str {
307 match error {
308 SessionOpenError::Dns => "dns",
309 SessionOpenError::Refused => "connection_refused",
310 SessionOpenError::NetworkUnreachable => "network_unreachable",
311 SessionOpenError::HostUnreachable => "host_unreachable",
312 SessionOpenError::Timeout => "timeout",
313 SessionOpenError::UpstreamAuthentication => "auth_failed",
314 SessionOpenError::PolicyDenied => "policy_denied",
315 SessionOpenError::Hop { .. } => "handshake",
316 SessionOpenError::Other(_) => "io",
317 }
318}
319
320async fn open_route(
321 config: &ConnectionConfig,
322 request: &RouteRequest<'_>,
323) -> Result<OpenedRoute, SessionOpenError> {
324 let selected = config.routing.route(request).map_err(|e| match e {
325 eggress_routing::RouteError::Rejected { .. } => SessionOpenError::PolicyDenied,
326 eggress_routing::RouteError::NoEligibleUpstream(_)
327 | eggress_routing::RouteError::UnknownGroup(_) => SessionOpenError::PolicyDenied,
328 })?;
329
330 let route = route_description(&selected);
331 let (rule_id, upstream_group, upstream_id, selection_reason) = route_metadata(&selected);
332
333 if let Some(metrics) = &config.metrics {
334 let rule_str = rule_id.as_deref().unwrap_or("default");
335 let action_str = match &selected {
336 SelectedRoute::Direct { .. } => "direct",
337 SelectedRoute::Upstream { .. } => "upstream",
338 };
339 metrics.record_route_decision(rule_str, action_str, "selected");
340 }
341
342 let upstream_protocol = match &selected {
343 SelectedRoute::Upstream { chain, .. } => Some(upstream_protocol_label(chain)),
344 SelectedRoute::Direct { .. } => None,
345 };
346
347 let tls_override = config.tls_client_config.as_ref();
348
349 let result = tokio::time::timeout(config.connect_timeout, async {
350 match selected {
351 SelectedRoute::Direct { .. } => {
352 let bind = config
353 .local_bind
354 .as_deref()
355 .map(|v| {
356 v.parse().map_err(|e| {
357 SessionOpenError::Other(format!("invalid local bind '{}': {}", v, e))
358 })
359 })
360 .transpose()?;
361 let stream = DirectConnector
362 .connect_with_options(
363 request.target,
364 &eggress_core::connector::ConnectOptions { local_bind: bind },
365 )
366 .await?;
367 Ok::<_, SessionOpenError>((stream, None))
368 }
369 SelectedRoute::Upstream {
370 chain,
371 pending_lease,
372 ..
373 } => {
374 #[cfg(feature = "extended")]
375 let shadowsocks_metrics = config.shadowsocks_metrics.clone();
376 #[cfg(not(feature = "extended"))]
377 let shadowsocks_metrics = config.shadowsocks_metrics;
378 #[cfg(feature = "ssh")]
379 let executor = build_chain_executor(
380 tls_override,
381 shadowsocks_metrics,
382 config.ssh_sessions.clone(),
383 );
384 #[cfg(not(feature = "ssh"))]
385 let executor = build_chain_executor(tls_override, shadowsocks_metrics);
386 let stream = executor.execute(&chain.hops, request.target).await?;
387 let active_lease = pending_lease.established();
388 Ok::<_, SessionOpenError>((stream, Some(active_lease)))
389 }
390 }
391 })
392 .await;
393
394 match result {
395 Ok(Ok((stream, active_lease))) => {
396 if let (Some(metrics), Some(protocol)) = (&config.metrics, upstream_protocol) {
397 metrics.record_upstream_open(protocol, "success");
398 }
399 Ok(OpenedRoute {
400 stream,
401 active_lease,
402 route_description: route,
403 rule_id,
404 upstream_group,
405 upstream_id,
406 selection_reason,
407 })
408 }
409 Ok(Err(e)) => {
410 if let (Some(metrics), Some(protocol)) = (&config.metrics, upstream_protocol) {
411 metrics.record_upstream_failure(protocol, failure_reason_label(&e));
412 }
413 Err(e)
414 }
415 Err(_timeout) => {
416 if let Some(metrics) = &config.metrics {
417 if let Some(protocol) = upstream_protocol {
418 metrics.record_upstream_failure(protocol, "timeout");
419 }
420 }
421 Err(SessionOpenError::Timeout)
422 }
423 }
424}
425
426async fn execute_tunnel(
428 mut pending: PendingTunnel,
429 config: &ConnectionConfig,
430 protocol: Option<String>,
431 target: Option<String>,
432) -> SessionReport {
433 tracing::info!("connecting to {}", pending.target);
434
435 let request = RouteRequest {
436 target: &pending.target,
437 source: config.context.source,
438 listener: &config.context.listener,
439 inbound_protocol: match pending.protocol {
440 crate::accept::TunnelProtocol::HttpConnect => eggress_core::ProtocolId::Http,
441 crate::accept::TunnelProtocol::Http2 => eggress_core::ProtocolId::Http2,
442 crate::accept::TunnelProtocol::Http3 => eggress_core::ProtocolId::Http3,
443 crate::accept::TunnelProtocol::WebSocket => eggress_core::ProtocolId::WebSocket,
444 crate::accept::TunnelProtocol::Socks4 => eggress_core::ProtocolId::Socks4,
445 crate::accept::TunnelProtocol::Socks5 => eggress_core::ProtocolId::Socks5,
446 crate::accept::TunnelProtocol::Shadowsocks => eggress_core::ProtocolId::Shadowsocks,
447 crate::accept::TunnelProtocol::ShadowsocksR => eggress_core::ProtocolId::ShadowsocksR,
448 crate::accept::TunnelProtocol::Trojan => eggress_core::ProtocolId::Trojan,
449 crate::accept::TunnelProtocol::Raw => eggress_core::ProtocolId::Raw,
450 },
451 identity: &pending.identity,
452 transport: eggress_routing::TransportKind::Tcp,
453 };
454
455 match open_route(config, &request).await {
456 Ok(opened) => {
457 let route = opened.route_description;
458 let rule_id = opened.rule_id;
459 let upstream_group = opened.upstream_group;
460 let upstream_id = opened.upstream_id;
461 let selection_reason = opened.selection_reason;
462 let _active_lease = opened.active_lease;
463 if let Err(e) = reply::send_tunnel_success(&mut pending, None).await {
464 tracing::debug!("failed to send success reply: {e}");
465 return SessionReport {
466 protocol,
467 target,
468 route,
469 bytes_upstream: 0,
470 bytes_downstream: 0,
471 outcome: SessionOutcome::ClientProtocolError,
472 failure: Some(FailureCategory::Protocol),
473 rule_id,
474 upstream_group,
475 upstream_id,
476 selection_reason,
477 };
478 }
479 let result = relay(pending.client, opened.stream).await;
480 tracing::debug!(
481 "relay complete: upstream={}B downstream={}B reason={:?}",
482 result.bytes_upstream,
483 result.bytes_downstream,
484 result.termination_reason
485 );
486 match result.termination_reason {
487 eggress_core::relay::TerminationReason::Error => SessionReport {
488 protocol,
489 target,
490 route,
491 bytes_upstream: result.bytes_upstream,
492 bytes_downstream: result.bytes_downstream,
493 outcome: SessionOutcome::RelayFailed,
494 failure: Some(FailureCategory::Relay),
495 rule_id,
496 upstream_group,
497 upstream_id,
498 selection_reason,
499 },
500 _ => SessionReport {
501 protocol,
502 target,
503 route,
504 bytes_upstream: result.bytes_upstream,
505 bytes_downstream: result.bytes_downstream,
506 outcome: SessionOutcome::Completed,
507 failure: None,
508 rule_id,
509 upstream_group,
510 upstream_id,
511 selection_reason,
512 },
513 }
514 }
515 Err(SessionOpenError::PolicyDenied) => {
516 let _ = reply::send_tunnel_failure(&mut pending, &SessionOpenError::PolicyDenied).await;
517 SessionReport::rejected(protocol, target, "reject".to_string())
518 }
519 Err(error) => {
520 let _ = reply::send_tunnel_failure(&mut pending, &error).await;
521 SessionReport::open_failed(error, protocol, target, "error".to_string())
522 }
523 }
524}
525
526#[allow(unused_assignments)]
533async fn execute_http_forward(
534 pending: PendingHttpForward,
535 config: &ConnectionConfig,
536 _target: Option<String>,
537) -> SessionReport {
538 tracing::info!("forward proxy to {}", pending.target);
539
540 let mut client = pending.client;
541 let mut total_bytes_upstream: u64 = 0;
542 let mut total_bytes_downstream: u64 = 0;
543 let mut last_target: Option<String> = None;
544 let mut last_rule_id: Option<String> = None;
545 let mut last_upstream_group: Option<String> = None;
546 let mut last_upstream_id: Option<String> = None;
547 let mut last_selection_reason: Option<eggress_routing::SelectionReason> = None;
548 let mut last_route = String::new();
549
550 let mut request = pending.request;
552 let mut client_close = request.connection_close;
553
554 loop {
555 let target_addr = request.target.clone();
556 last_target = Some(target_addr.to_string());
557
558 if eggress_protocol_http::has_unsupported_expectation(&request.headers) {
559 let _ = reply::send_http_expectation_failed(&mut client).await;
560 return SessionReport {
561 protocol: None,
562 target: last_target,
563 route: last_route,
564 bytes_upstream: total_bytes_upstream,
565 bytes_downstream: total_bytes_downstream,
566 outcome: SessionOutcome::ClientProtocolError,
567 failure: Some(FailureCategory::Protocol),
568 rule_id: last_rule_id,
569 upstream_group: last_upstream_group,
570 upstream_id: last_upstream_id,
571 selection_reason: last_selection_reason,
572 };
573 }
574
575 let route_request = RouteRequest {
576 target: &target_addr,
577 source: config.context.source,
578 listener: &config.context.listener,
579 inbound_protocol: eggress_core::ProtocolId::Http,
580 identity: &pending.identity,
581 transport: eggress_routing::TransportKind::Tcp,
582 };
583
584 match open_route(config, &route_request).await {
585 Ok(mut opened) => {
586 last_route = opened.route_description;
587 last_rule_id = opened.rule_id;
588 last_upstream_group = opened.upstream_group;
589 last_upstream_id = opened.upstream_id;
590 last_selection_reason = opened.selection_reason;
591 let _active_lease = opened.active_lease;
592
593 let origin_req = eggress_protocol_http::build_origin_request(&request);
594 let head_bytes = origin_req.len() as u64;
595
596 if let Err(e) = opened.stream.write_all(origin_req.as_bytes()).await {
597 let _ = reply::send_http_forward_failure(
598 &mut client,
599 &SessionOpenError::Other(e.to_string()),
600 )
601 .await;
602 return SessionReport {
603 protocol: None,
604 target: last_target,
605 route: last_route,
606 bytes_upstream: total_bytes_upstream + head_bytes,
607 bytes_downstream: total_bytes_downstream,
608 outcome: SessionOutcome::RelayFailed,
609 failure: Some(FailureCategory::Relay),
610 rule_id: last_rule_id,
611 upstream_group: last_upstream_group,
612 upstream_id: last_upstream_id,
613 selection_reason: last_selection_reason,
614 };
615 }
616 if let Err(e) = opened.stream.flush().await {
617 let _ = reply::send_http_forward_failure(
618 &mut client,
619 &SessionOpenError::Other(e.to_string()),
620 )
621 .await;
622 return SessionReport {
623 protocol: None,
624 target: last_target,
625 route: last_route,
626 bytes_upstream: total_bytes_upstream + head_bytes,
627 bytes_downstream: total_bytes_downstream,
628 outcome: SessionOutcome::RelayFailed,
629 failure: Some(FailureCategory::Relay),
630 rule_id: last_rule_id,
631 upstream_group: last_upstream_group,
632 upstream_id: last_upstream_id,
633 selection_reason: last_selection_reason,
634 };
635 }
636
637 let body_result = tokio::time::timeout(config.connect_timeout, async {
638 let report = eggress_protocol_http::copy_request_body(
639 &mut client,
640 &mut opened.stream,
641 request.body_kind(),
642 &eggress_protocol_http::BodyCopyLimits::default(),
643 )
644 .await?;
645 opened.stream.flush().await?;
646 Ok::<_, eggress_protocol_http::HttpError>(report)
647 })
648 .await;
649 let body_report = match body_result {
650 Ok(Ok(report)) => report,
651 Ok(Err(_)) => {
652 let _ = opened.stream.shutdown().await;
653 let _ = client.shutdown().await;
654 return SessionReport {
655 protocol: None,
656 target: last_target,
657 route: last_route,
658 bytes_upstream: total_bytes_upstream + head_bytes,
659 bytes_downstream: total_bytes_downstream,
660 outcome: SessionOutcome::ClientProtocolError,
661 failure: Some(FailureCategory::Protocol),
662 rule_id: last_rule_id,
663 upstream_group: last_upstream_group,
664 upstream_id: last_upstream_id,
665 selection_reason: last_selection_reason,
666 };
667 }
668 Err(_) => {
669 let _ = opened.stream.shutdown().await;
670 let _ = client.shutdown().await;
671 return SessionReport {
672 protocol: None,
673 target: last_target,
674 route: last_route,
675 bytes_upstream: total_bytes_upstream + head_bytes,
676 bytes_downstream: total_bytes_downstream,
677 outcome: SessionOutcome::RelayFailed,
678 failure: Some(FailureCategory::Relay),
679 rule_id: last_rule_id,
680 upstream_group: last_upstream_group,
681 upstream_id: last_upstream_id,
682 selection_reason: last_selection_reason,
683 };
684 }
685 };
686
687 total_bytes_upstream += head_bytes + body_report.wire_bytes;
688
689 let forward_result =
690 match eggress_protocol_http::forward_response(&mut opened.stream, &mut client)
691 .await
692 {
693 Ok(result) => result,
694 Err(eggress_protocol_http::HttpError::UpgradeUnsupported) => {
695 let _ = reply::send_http_upgrade_unsupported(&mut client).await;
696 return SessionReport {
697 protocol: None,
698 target: last_target,
699 route: last_route,
700 bytes_upstream: total_bytes_upstream,
701 bytes_downstream: total_bytes_downstream,
702 outcome: SessionOutcome::RelayFailed,
703 failure: Some(FailureCategory::Protocol),
704 rule_id: last_rule_id,
705 upstream_group: last_upstream_group,
706 upstream_id: last_upstream_id,
707 selection_reason: last_selection_reason,
708 };
709 }
710 Err(_e) => {
711 let _ = client.shutdown().await;
712 return SessionReport {
713 protocol: None,
714 target: last_target,
715 route: last_route,
716 bytes_upstream: total_bytes_upstream,
717 bytes_downstream: total_bytes_downstream,
718 outcome: SessionOutcome::RelayFailed,
719 failure: Some(FailureCategory::Relay),
720 rule_id: last_rule_id,
721 upstream_group: last_upstream_group,
722 upstream_id: last_upstream_id,
723 selection_reason: last_selection_reason,
724 };
725 }
726 };
727
728 total_bytes_downstream += forward_result.report.bytes_forwarded;
729
730 let should_close = client_close
732 || forward_result.client_should_close
733 || !forward_result.upstream_alive;
734
735 if should_close {
736 break;
737 }
738
739 match eggress_protocol_http::forward_request_stream(&mut client).await {
741 Ok(next_request) => {
742 client_close = next_request.connection_close;
743 request = next_request;
744 }
745 Err(eggress_protocol_http::HttpError::Io(ref e))
746 if e.kind() == std::io::ErrorKind::UnexpectedEof =>
747 {
748 break;
750 }
751 Err(_) => {
752 break;
754 }
755 }
756 }
757 Err(SessionOpenError::PolicyDenied) => {
758 let _ =
759 reply::send_http_forward_failure(&mut client, &SessionOpenError::PolicyDenied)
760 .await;
761 return SessionReport::rejected(None, last_target, "reject".to_string());
762 }
763 Err(error) => {
764 let _ = reply::send_http_forward_failure(&mut client, &error).await;
765 return SessionReport::open_failed(error, None, last_target, "error".to_string());
766 }
767 }
768 }
769
770 SessionReport {
771 protocol: None,
772 target: last_target,
773 route: last_route,
774 bytes_upstream: total_bytes_upstream,
775 bytes_downstream: total_bytes_downstream,
776 outcome: SessionOutcome::Completed,
777 failure: None,
778 rule_id: last_rule_id,
779 upstream_group: last_upstream_group,
780 upstream_id: last_upstream_id,
781 selection_reason: last_selection_reason,
782 }
783}
784
785type HandshakeFuture<'a> = std::pin::Pin<
786 Box<
787 dyn std::future::Future<
788 Output = Result<BoxStream, Box<dyn std::error::Error + Send + Sync>>,
789 > + Send
790 + 'a,
791 >,
792>;
793
794async fn execute_udp_associate(
795 pending: PendingUdpAssociate,
796 config: &ConnectionConfig,
797) -> SessionReport {
798 let protocol = Some("socks5".to_string());
799
800 let udp_service = match &config.udp {
801 Some(svc) if svc.is_enabled() => svc,
802 _ => {
803 tracing::debug!("UDP ASSOCIATE rejected: UDP service not available");
804 let mut stream = pending.client;
805 let target = pending.client_hint.unwrap_or(TargetAddr {
806 host: TargetHost::Ip(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)),
807 port: 0,
808 });
809 let socks_addr = target_to_socks_addr(&target);
810 let _ = eggress_protocol_socks::socks5::server::send_connect_reply(
811 &mut stream,
812 eggress_protocol_socks::socks5::server::REP_NOT_ALLOWED,
813 &socks_addr,
814 )
815 .await;
816 return SessionReport {
817 protocol,
818 target: None,
819 route: "udp_associate_disabled".to_string(),
820 bytes_upstream: 0,
821 bytes_downstream: 0,
822 outcome: SessionOutcome::RouteFailed,
823 failure: Some(FailureCategory::Protocol),
824 rule_id: None,
825 upstream_group: None,
826 upstream_id: None,
827 selection_reason: None,
828 };
829 }
830 };
831
832 let client_tcp_peer = config
833 .context
834 .source
835 .unwrap_or_else(|| "127.0.0.1:0".parse().unwrap());
836
837 let gen = config.context.generation;
838
839 let handle = match tokio::time::timeout(
840 config.connect_timeout,
841 udp_service.create_association(
842 &config.context.listener,
843 client_tcp_peer,
844 pending.identity.clone(),
845 gen,
846 ),
847 )
848 .await
849 {
850 Ok(Ok(handle)) => handle,
851 Ok(Err(e)) => {
852 tracing::debug!("UDP ASSOCIATE failed: {e}");
853 let mut stream = pending.client;
854 let target = pending.client_hint.unwrap_or(TargetAddr {
855 host: TargetHost::Ip(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)),
856 port: 0,
857 });
858 let socks_addr = target_to_socks_addr(&target);
859 let _ = eggress_protocol_socks::socks5::server::send_connect_reply(
860 &mut stream,
861 eggress_protocol_socks::socks5::server::REP_GENERAL_FAILURE,
862 &socks_addr,
863 )
864 .await;
865 return SessionReport {
866 protocol,
867 target: None,
868 route: "udp_associate_failed".to_string(),
869 bytes_upstream: 0,
870 bytes_downstream: 0,
871 outcome: SessionOutcome::RouteFailed,
872 failure: Some(FailureCategory::Protocol),
873 rule_id: None,
874 upstream_group: None,
875 upstream_id: None,
876 selection_reason: None,
877 };
878 }
879 Err(_) => {
880 tracing::debug!("UDP ASSOCIATE failed: timeout");
881 let mut stream = pending.client;
882 let target = pending.client_hint.unwrap_or(TargetAddr {
883 host: TargetHost::Ip(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)),
884 port: 0,
885 });
886 let socks_addr = target_to_socks_addr(&target);
887 let _ = eggress_protocol_socks::socks5::server::send_connect_reply(
888 &mut stream,
889 eggress_protocol_socks::socks5::server::REP_GENERAL_FAILURE,
890 &socks_addr,
891 )
892 .await;
893 return SessionReport {
894 protocol,
895 target: None,
896 route: "udp_associate_timeout".to_string(),
897 bytes_upstream: 0,
898 bytes_downstream: 0,
899 outcome: SessionOutcome::HandshakeTimedOut,
900 failure: Some(FailureCategory::RouteTimeout),
901 rule_id: None,
902 upstream_group: None,
903 upstream_id: None,
904 selection_reason: None,
905 };
906 }
907 };
908
909 let relay_ip = handle.relay_addr.ip();
910 let relay_port = handle.relay_addr.port();
911 let socks_addr = match relay_ip {
912 std::net::IpAddr::V4(ip) => {
913 eggress_protocol_socks::socks5::server::SocksAddr::IPv4(ip.octets(), relay_port)
914 }
915 std::net::IpAddr::V6(ip) => {
916 eggress_protocol_socks::socks5::server::SocksAddr::IPv6(ip.octets(), relay_port)
917 }
918 };
919
920 let mut stream = pending.client;
921 if let Err(e) =
922 eggress_protocol_socks::socks5::server::send_udp_associate_reply(&mut stream, &socks_addr)
923 .await
924 {
925 tracing::debug!("failed to send UDP ASSOCIATE reply: {e}");
926 handle.cancel.cancel();
927 return SessionReport {
928 protocol,
929 target: None,
930 route: "udp_associate_reply_failed".to_string(),
931 bytes_upstream: 0,
932 bytes_downstream: 0,
933 outcome: SessionOutcome::ClientProtocolError,
934 failure: Some(FailureCategory::Protocol),
935 rule_id: None,
936 upstream_group: None,
937 upstream_id: None,
938 selection_reason: None,
939 };
940 }
941
942 tracing::info!(
943 association_id = ?handle.id,
944 relay_addr = %handle.relay_addr,
945 "UDP ASSOCIATE established, keeping TCP control connection alive"
946 );
947
948 let mut buf = [0u8; 1];
949 tokio::select! {
950 result = stream.read_exact(&mut buf) => {
951 match result {
952 Ok(_) => {
953 tracing::debug!(
954 association_id = ?handle.id,
955 "TCP control connection closed by client"
956 );
957 }
958 Err(_) => {
959 tracing::debug!(
960 association_id = ?handle.id,
961 "TCP control connection read failed"
962 );
963 }
964 }
965 }
966 _ = handle.cancel.cancelled() => {
967 tracing::debug!(
968 association_id = ?handle.id,
969 "UDP association cancelled"
970 );
971 }
972 }
973
974 handle.cancel.cancel();
975
976 SessionReport {
977 protocol,
978 target: None,
979 route: "udp_associate".to_string(),
980 bytes_upstream: 0,
981 bytes_downstream: 0,
982 outcome: SessionOutcome::Completed,
983 failure: None,
984 rule_id: None,
985 upstream_group: None,
986 upstream_id: None,
987 selection_reason: None,
988 }
989}
990
991pub fn build_chain_executor(
992 tls_override: Option<&std::sync::Arc<rustls::ClientConfig>>,
993 #[cfg(feature = "extended")] shadowsocks_metrics: Option<
994 std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
995 >,
996 #[cfg(not(feature = "extended"))] _shadowsocks_metrics: Option<()>,
997 #[cfg(feature = "ssh")] ssh_sessions: Option<
998 std::sync::Arc<eggress_transport_ssh::SshSessionCache>,
999 >,
1000) -> ChainExecutor {
1001 let shared_tls_config = match tls_override {
1003 Some(config) => Some(config.clone()),
1004 None => {
1005 let builder = eggress_transport_tls::TlsClientConfigBuilder::new();
1006 match builder.with_system_roots().and_then(|b| b.build()) {
1007 Ok(config) => Some(config),
1008 Err(e) => {
1009 tracing::warn!("failed to build shared TLS config: {e}");
1010 None
1011 }
1012 }
1013 }
1014 };
1015
1016 #[cfg(feature = "extended")]
1017 let shared_tls_config_arc = shared_tls_config.clone();
1018 #[cfg(not(feature = "extended"))]
1019 let _shared_tls_config_arc = shared_tls_config.clone();
1020
1021 let mut handlers: Vec<Box<dyn HopHandler>> = vec![
1022 Box::new(HttpHopHandler),
1023 Box::new(HttpOnlyHopHandler),
1024 Box::new(Socks5HopHandler),
1025 Box::new(Socks4HopHandler),
1026 ];
1027
1028 #[cfg(feature = "extended")]
1029 {
1030 handlers.push(Box::new(ShadowsocksHopHandler {
1031 metrics: shadowsocks_metrics,
1032 }));
1033 handlers.push(Box::new(TrojanHopHandler {
1034 tls_config: shared_tls_config_arc,
1035 }));
1036 handlers.push(Box::new(WebSocketHopHandler));
1037 }
1038
1039 #[cfg(feature = "pproxy-legacy")]
1040 handlers.push(Box::new(ShadowsocksRHopHandler));
1041
1042 handlers.push(Box::new(RawHopHandler));
1043 handlers.push(Box::new(UnixHopHandler));
1044 #[cfg(feature = "ssh")]
1045 if let Some(sessions) = ssh_sessions {
1046 handlers.push(Box::new(SshHopHandler { sessions }));
1047 }
1048 handlers.push(Box::new(H2HopHandler));
1049
1050 #[cfg(feature = "quic")]
1051 {
1052 handlers.push(Box::new(QuicHopHandler));
1053 handlers.push(Box::new(H3HopHandler));
1054 }
1055
1056 let tls_wrapper_override = tls_override.cloned();
1058 let tls_wrapper: eggress_core::chain::TlsWrapper =
1059 Box::new(move |stream, server_name, alpn| {
1060 let config_override = tls_wrapper_override.clone();
1061 Box::pin(async move {
1062 let config = match config_override {
1063 Some(c) => c,
1064 None => {
1065 let mut builder = eggress_transport_tls::TlsClientConfigBuilder::new();
1066 builder = builder.with_system_roots().map_err(
1067 |e| -> Box<dyn std::error::Error + Send + Sync> { Box::new(e) as _ },
1068 )?;
1069 if let Some(ref protocols) = alpn {
1070 builder = builder.with_alpn(protocols.clone());
1071 }
1072 builder.build().map_err(
1073 |e| -> Box<dyn std::error::Error + Send + Sync> { Box::new(e) as _ },
1074 )?
1075 }
1076 };
1077 eggress_transport_tls::tls_connect(stream, config, &server_name)
1078 .await
1079 .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { Box::new(e) as _ })
1080 })
1081 });
1082
1083 ChainExecutor::new(handlers)
1084 .with_tls_wrapper(tls_wrapper)
1085 .with_shared_tls_config(shared_tls_config)
1086}
1087
1088struct HttpOnlyStream {
1092 inner: BoxStream,
1093 target: TargetAddr,
1094 pending: Vec<u8>,
1095}
1096
1097impl tokio::io::AsyncRead for HttpOnlyStream {
1098 fn poll_read(
1099 mut self: Pin<&mut Self>,
1100 cx: &mut Context<'_>,
1101 buf: &mut tokio::io::ReadBuf<'_>,
1102 ) -> Poll<std::io::Result<()>> {
1103 Pin::new(&mut self.inner).poll_read(cx, buf)
1104 }
1105}
1106
1107impl tokio::io::AsyncWrite for HttpOnlyStream {
1108 fn poll_write(
1109 mut self: Pin<&mut Self>,
1110 _cx: &mut Context<'_>,
1111 data: &[u8],
1112 ) -> Poll<std::io::Result<usize>> {
1113 self.pending.extend_from_slice(data);
1114 Poll::Ready(Ok(data.len()))
1115 }
1116
1117 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1118 if !self.pending.is_empty() {
1119 let end = match self.pending.windows(4).position(|w| w == b"\r\n\r\n") {
1120 Some(pos) => pos + 4,
1121 None => return Poll::Ready(Ok(())),
1122 };
1123 let head = &self.pending[..end];
1124 let mut lines = head.split(|b| *b == b'\n');
1125 let first = lines.next().unwrap_or_default();
1126 let first = first.strip_suffix(b"\r").unwrap_or(first);
1127 let mut rewritten = Vec::with_capacity(head.len() + 32);
1128 if let Some(space) = first.iter().position(|b| *b == b' ') {
1129 if let Some(second) = first[space + 1..].iter().position(|b| *b == b' ') {
1130 let method = &first[..space];
1131 let path = &first[space + 1..space + 1 + second];
1132 if path.starts_with(b"/") {
1133 rewritten.extend_from_slice(method);
1134 rewritten.extend_from_slice(b" http://");
1135 rewritten.extend_from_slice(self.target.to_string().as_bytes());
1136 rewritten.extend_from_slice(path);
1137 rewritten.extend_from_slice(&first[space + 1 + second..]);
1138 rewritten.extend_from_slice(b"\r\n");
1139 for line in lines {
1140 rewritten.extend_from_slice(line);
1141 }
1142 let body = self.pending[end..].to_vec();
1143 rewritten.extend_from_slice(&body);
1144 self.pending = rewritten;
1145 }
1146 }
1147 }
1148 }
1149 let pending = self.pending.clone();
1150 match Pin::new(&mut self.inner).poll_write(cx, &pending) {
1151 Poll::Ready(Ok(n)) => {
1152 self.pending.drain(..n);
1153 if self.pending.is_empty() {
1154 Pin::new(&mut self.inner).poll_flush(cx)
1155 } else {
1156 Poll::Ready(Ok(()))
1157 }
1158 }
1159 Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
1160 Poll::Pending => Poll::Pending,
1161 }
1162 }
1163
1164 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1165 let _ = self.as_mut().poll_flush(cx);
1166 Pin::new(&mut self.inner).poll_shutdown(cx)
1167 }
1168}
1169
1170struct HttpOnlyHopHandler;
1171
1172impl HopHandler for HttpOnlyHopHandler {
1173 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1174 eggress_uri::ProtocolSpec::HttpOnly
1175 }
1176 fn handshake<'a>(
1177 &'a self,
1178 stream: BoxStream,
1179 target: &'a TargetAddr,
1180 _hop: &'a eggress_uri::ProxyHopSpec,
1181 _hop_index: usize,
1182 ) -> HandshakeFuture<'a> {
1183 let target = target.clone();
1184 Box::pin(async move {
1185 Ok(Box::new(HttpOnlyStream {
1186 inner: stream,
1187 target,
1188 pending: Vec::new(),
1189 }) as BoxStream)
1190 })
1191 }
1192}
1193
1194struct HttpHopHandler;
1195
1196impl HopHandler for HttpHopHandler {
1197 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1198 eggress_uri::ProtocolSpec::Http
1199 }
1200
1201 fn handshake<'a>(
1202 &'a self,
1203 stream: BoxStream,
1204 target: &'a TargetAddr,
1205 hop: &'a eggress_uri::ProxyHopSpec,
1206 _hop_index: usize,
1207 ) -> HandshakeFuture<'a> {
1208 let auth = hop
1209 .credentials
1210 .as_ref()
1211 .map(|c| (c.username.as_str(), c.password.as_str()));
1212 Box::pin(async move {
1213 eggress_protocol_http::http_connect(stream, target, auth, &Default::default())
1214 .await
1215 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1216 })
1217 }
1218}
1219
1220struct Socks5HopHandler;
1221
1222impl HopHandler for Socks5HopHandler {
1223 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1224 eggress_uri::ProtocolSpec::Socks5
1225 }
1226
1227 fn handshake<'a>(
1228 &'a self,
1229 stream: BoxStream,
1230 target: &'a TargetAddr,
1231 hop: &'a eggress_uri::ProxyHopSpec,
1232 _hop_index: usize,
1233 ) -> HandshakeFuture<'a> {
1234 let socks_addr = target_to_socks_addr(target);
1235 let auth = hop
1236 .credentials
1237 .as_ref()
1238 .map(|c| (c.username.as_str(), c.password.as_str()));
1239 Box::pin(async move {
1240 eggress_protocol_socks::socks5::client::socks5_connect(stream, &socks_addr, auth)
1241 .await
1242 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1243 })
1244 }
1245}
1246
1247struct Socks4HopHandler;
1248
1249impl HopHandler for Socks4HopHandler {
1250 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1251 eggress_uri::ProtocolSpec::Socks4
1252 }
1253
1254 fn handshake<'a>(
1255 &'a self,
1256 stream: BoxStream,
1257 target: &'a TargetAddr,
1258 hop: &'a eggress_uri::ProxyHopSpec,
1259 _hop_index: usize,
1260 ) -> HandshakeFuture<'a> {
1261 let user_id = hop.credentials.as_ref().map(|c| c.username.as_str());
1262 Box::pin(async move {
1263 eggress_protocol_socks::socks4_connect(stream, target, user_id)
1264 .await
1265 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1266 })
1267 }
1268}
1269
1270#[cfg(feature = "extended")]
1271struct ShadowsocksHopHandler {
1272 metrics: Option<std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>>,
1273}
1274
1275#[cfg(feature = "extended")]
1276impl HopHandler for ShadowsocksHopHandler {
1277 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1278 eggress_uri::ProtocolSpec::Shadowsocks
1279 }
1280
1281 fn handshake<'a>(
1282 &'a self,
1283 stream: BoxStream,
1284 target: &'a TargetAddr,
1285 hop: &'a eggress_uri::ProxyHopSpec,
1286 _hop_index: usize,
1287 ) -> HandshakeFuture<'a> {
1288 let metrics = self.metrics.clone();
1289 Box::pin(async move {
1290 let creds = hop.credentials.as_ref().ok_or_else(|| {
1291 Box::new(eggress_protocol_shadowsocks::ShadowsocksError::Other(
1292 "shadowsocks requires credentials (method:password)".to_string(),
1293 )) as Box<dyn std::error::Error + Send + Sync>
1294 })?;
1295
1296 match eggress_protocol_shadowsocks::CipherMethod::parse_method(&creds.username) {
1297 Ok(method) => eggress_protocol_shadowsocks::shadowsocks_connect(
1298 stream,
1299 target,
1300 method,
1301 &creds.password,
1302 metrics,
1303 )
1304 .await
1305 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>),
1306 Err(modern_error) => {
1307 #[cfg(feature = "legacy-crypto")]
1308 if let Ok(legacy_method) =
1309 eggress_protocol_shadowsocks::legacy::LegacyMethod::parse(&creds.username)
1310 {
1311 return eggress_protocol_shadowsocks::legacy::legacy_connect(
1312 stream,
1313 target,
1314 legacy_method,
1315 creds.password.as_bytes(),
1316 )
1317 .await
1318 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>);
1319 }
1320 if let Some(m) = metrics.as_ref() {
1321 m.record_tcp_unsupported_method_reject();
1322 }
1323 Err(Box::new(modern_error) as Box<dyn std::error::Error + Send + Sync>)
1324 }
1325 }
1326 })
1327 }
1328}
1329
1330#[cfg(feature = "pproxy-legacy")]
1331struct ShadowsocksRHopHandler;
1332
1333#[cfg(feature = "pproxy-legacy")]
1334impl HopHandler for ShadowsocksRHopHandler {
1335 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1336 eggress_uri::ProtocolSpec::ShadowsocksR
1337 }
1338
1339 fn handshake<'a>(
1340 &'a self,
1341 stream: BoxStream,
1342 target: &'a TargetAddr,
1343 hop: &'a eggress_uri::ProxyHopSpec,
1344 _hop_index: usize,
1345 ) -> HandshakeFuture<'a> {
1346 Box::pin(async move {
1347 let plugins = eggress_protocol_shadowsocks::compat::plugin::parse_plugins(&hop.plugins)
1348 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
1349 eggress_protocol_shadowsocks::compat::ssr::ssr_connect(
1350 stream,
1351 target,
1352 &eggress_protocol_shadowsocks::compat::ssr::SsrConfig {
1353 auth_prefix: hop.auth_prefix.as_deref().map(str::as_bytes).map(Vec::from),
1354 plugins,
1355 },
1356 )
1357 .await
1358 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1359 })
1360 }
1361}
1362
1363#[cfg(feature = "extended")]
1364struct TrojanHopHandler {
1365 tls_config: Option<std::sync::Arc<rustls::ClientConfig>>,
1366}
1367
1368#[cfg(feature = "extended")]
1369impl HopHandler for TrojanHopHandler {
1370 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1371 eggress_uri::ProtocolSpec::Trojan
1372 }
1373
1374 fn handshake<'a>(
1375 &'a self,
1376 stream: BoxStream,
1377 target: &'a TargetAddr,
1378 hop: &'a eggress_uri::ProxyHopSpec,
1379 _hop_index: usize,
1380 ) -> HandshakeFuture<'a> {
1381 let tls_config = self.tls_config.clone();
1382 let password = hop.credentials.as_ref().map(|c| c.password.clone());
1383 let server_name = hop
1384 .server_name
1385 .clone()
1386 .unwrap_or_else(|| hop.endpoint.host.clone());
1387 Box::pin(async move {
1388 let password = password.ok_or_else(|| {
1389 Box::new(eggress_protocol_trojan::TrojanError::Protocol(
1390 "trojan requires credentials (password)".to_string(),
1391 )) as Box<dyn std::error::Error + Send + Sync>
1392 })?;
1393
1394 eggress_protocol_trojan::trojan_connect(
1395 stream,
1396 target,
1397 &password,
1398 &server_name,
1399 tls_config,
1400 )
1401 .await
1402 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1403 })
1404 }
1405}
1406
1407#[cfg(feature = "extended")]
1408struct WebSocketHopHandler;
1409
1410#[cfg(feature = "extended")]
1411impl HopHandler for WebSocketHopHandler {
1412 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1413 eggress_uri::ProtocolSpec::WebSocket
1414 }
1415
1416 fn handshake<'a>(
1417 &'a self,
1418 stream: BoxStream,
1419 _target: &'a TargetAddr,
1420 hop: &'a eggress_uri::ProxyHopSpec,
1421 _hop_index: usize,
1422 ) -> HandshakeFuture<'a> {
1423 let use_tls = hop.tls;
1424 let scheme = if use_tls { "wss" } else { "ws" };
1425 let url = format!("{}://{}:{}", scheme, hop.endpoint.host, hop.endpoint.port);
1426 Box::pin(async move {
1427 let client = eggress_protocol_websocket::WebSocketTunnelClient::with_default_config();
1428 client
1429 .connect_over_stream(&url, stream)
1430 .await
1431 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1432 })
1433 }
1434}
1435
1436struct RawHopHandler;
1437
1438impl HopHandler for RawHopHandler {
1439 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1440 eggress_uri::ProtocolSpec::Raw
1441 }
1442
1443 fn handshake<'a>(
1444 &'a self,
1445 stream: BoxStream,
1446 _target: &'a TargetAddr,
1447 _hop: &'a eggress_uri::ProxyHopSpec,
1448 _hop_index: usize,
1449 ) -> HandshakeFuture<'a> {
1450 Box::pin(async move { Ok(stream) })
1451 }
1452}
1453
1454#[cfg(feature = "ssh")]
1455struct SshHopHandler {
1456 sessions: std::sync::Arc<eggress_transport_ssh::SshSessionCache>,
1457}
1458
1459#[cfg(feature = "ssh")]
1460impl HopHandler for SshHopHandler {
1461 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1462 eggress_uri::ProtocolSpec::Ssh
1463 }
1464
1465 fn handshake<'a>(
1466 &'a self,
1467 stream: BoxStream,
1468 target: &'a TargetAddr,
1469 hop: &'a eggress_uri::ProxyHopSpec,
1470 hop_index: usize,
1471 ) -> HandshakeFuture<'a> {
1472 let sessions = self.sessions.clone();
1473 let target = target.clone();
1474 let endpoint = hop.endpoint.clone();
1475 let credentials = hop.credentials.clone();
1476 Box::pin(async move {
1477 let credentials = credentials.ok_or_else(|| {
1478 Box::new(eggress_transport_ssh::SshTransportError::MissingUsername)
1479 as Box<dyn std::error::Error + Send + Sync>
1480 })?;
1481 if credentials.username.is_empty() {
1482 return Err(
1483 Box::new(eggress_transport_ssh::SshTransportError::MissingUsername)
1484 as Box<dyn std::error::Error + Send + Sync>,
1485 );
1486 }
1487 let auth = if let Some(path) = credentials.password.strip_prefix(':') {
1488 if path.is_empty() {
1489 return Err(Box::new(
1490 eggress_transport_ssh::SshTransportError::EmptyPrivateKeyPath,
1491 )
1492 as Box<dyn std::error::Error + Send + Sync>);
1493 }
1494 eggress_transport_ssh::SshAuth::PrivateKey(path.to_string())
1495 } else {
1496 eggress_transport_ssh::SshAuth::Password(credentials.password)
1497 };
1498 let key = eggress_transport_ssh::SshSessionKey {
1499 host: endpoint.host,
1500 port: endpoint.port,
1501 username: credentials.username,
1502 auth,
1503 hop_index,
1504 };
1505 let result = if target.port == 0 {
1506 sessions
1507 .open_unix_channel(key, stream, &target.host.to_string())
1508 .await
1509 } else {
1510 sessions
1511 .open_tcp_channel(key, stream, &target.host.to_string(), target.port)
1512 .await
1513 };
1514 result.map_err(|error| Box::new(error) as Box<dyn std::error::Error + Send + Sync>)
1515 })
1516 }
1517}
1518
1519struct UnixHopHandler;
1520
1521impl HopHandler for UnixHopHandler {
1522 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1523 eggress_uri::ProtocolSpec::Unix
1524 }
1525
1526 fn handshake<'a>(
1527 &'a self,
1528 stream: BoxStream,
1529 _target: &'a TargetAddr,
1530 _hop: &'a eggress_uri::ProxyHopSpec,
1531 _hop_index: usize,
1532 ) -> HandshakeFuture<'a> {
1533 Box::pin(async move { Ok(stream) })
1534 }
1535}
1536
1537struct H2HopHandler;
1538
1539#[cfg(feature = "quic")]
1540struct QuicHopHandler;
1541
1542#[cfg(feature = "quic")]
1543impl HopHandler for QuicHopHandler {
1544 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1545 eggress_uri::ProtocolSpec::Quic
1546 }
1547
1548 fn open<'a>(
1549 &'a self,
1550 endpoint: &'a eggress_uri::EndpointSpec,
1551 hop: &'a eggress_uri::ProxyHopSpec,
1552 _target: &'a TargetAddr,
1553 ) -> Option<HandshakeFuture<'a>> {
1554 let endpoint = endpoint.clone();
1555 let server_name = hop
1556 .server_name
1557 .clone()
1558 .unwrap_or_else(|| endpoint.host.clone());
1559 Some(Box::pin(async move {
1560 let client = eggress_transport_quic::QuicClient::connect(
1561 &endpoint.host,
1562 endpoint.port,
1563 eggress_transport_quic::QuicClientConfig {
1564 server_name,
1565 insecure: hop.insecure,
1566 alpn_protocols: Vec::new(),
1567 ..Default::default()
1568 },
1569 )
1570 .await?;
1571 client
1572 .open_stream()
1573 .await
1574 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1575 }))
1576 }
1577
1578 fn handshake<'a>(
1579 &'a self,
1580 stream: BoxStream,
1581 _target: &'a TargetAddr,
1582 _hop: &'a eggress_uri::ProxyHopSpec,
1583 _hop_index: usize,
1584 ) -> HandshakeFuture<'a> {
1585 Box::pin(async move { Ok(stream) })
1586 }
1587}
1588
1589#[cfg(feature = "quic")]
1590struct H3HopHandler;
1591
1592#[cfg(feature = "quic")]
1593impl HopHandler for H3HopHandler {
1594 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1595 eggress_uri::ProtocolSpec::Http3
1596 }
1597
1598 fn open<'a>(
1599 &'a self,
1600 endpoint: &'a eggress_uri::EndpointSpec,
1601 hop: &'a eggress_uri::ProxyHopSpec,
1602 target: &'a TargetAddr,
1603 ) -> Option<HandshakeFuture<'a>> {
1604 let endpoint = endpoint.clone();
1605 let target = target.clone();
1606 let server_name = hop
1607 .server_name
1608 .clone()
1609 .unwrap_or_else(|| endpoint.host.clone());
1610 let authorization = hop
1611 .credentials
1612 .as_ref()
1613 .map(|credentials| (credentials.username.clone(), credentials.password.clone()));
1614 Some(Box::pin(async move {
1615 let client = eggress_transport_quic::QuicClient::connect(
1616 &endpoint.host,
1617 endpoint.port,
1618 eggress_transport_quic::QuicClientConfig {
1619 server_name,
1620 insecure: hop.insecure,
1621 alpn_protocols: vec![b"h3".to_vec()],
1622 ..Default::default()
1623 },
1624 )
1625 .await?;
1626 eggress_protocol_h3::H3Client::new(client, authorization)
1627 .connect(&target)
1628 .await
1629 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1630 }))
1631 }
1632
1633 fn handshake<'a>(
1634 &'a self,
1635 stream: BoxStream,
1636 _target: &'a TargetAddr,
1637 _hop: &'a eggress_uri::ProxyHopSpec,
1638 _hop_index: usize,
1639 ) -> HandshakeFuture<'a> {
1640 Box::pin(async move { Ok(stream) })
1641 }
1642}
1643
1644struct PooledH2Stream {
1648 inner:
1649 tokio::io::Join<eggress_protocol_http::H2StreamRead, eggress_protocol_http::H2StreamWrite>,
1650 _guard: eggress_protocol_http::H2PoolGuard,
1651}
1652
1653impl tokio::io::AsyncRead for PooledH2Stream {
1654 fn poll_read(
1655 mut self: std::pin::Pin<&mut Self>,
1656 cx: &mut std::task::Context<'_>,
1657 buf: &mut tokio::io::ReadBuf<'_>,
1658 ) -> std::task::Poll<std::io::Result<()>> {
1659 std::pin::Pin::new(&mut self.inner).poll_read(cx, buf)
1660 }
1661}
1662
1663impl tokio::io::AsyncWrite for PooledH2Stream {
1664 fn poll_write(
1665 mut self: std::pin::Pin<&mut Self>,
1666 cx: &mut std::task::Context<'_>,
1667 buf: &[u8],
1668 ) -> std::task::Poll<std::io::Result<usize>> {
1669 std::pin::Pin::new(&mut self.inner).poll_write(cx, buf)
1670 }
1671
1672 fn poll_flush(
1673 mut self: std::pin::Pin<&mut Self>,
1674 cx: &mut std::task::Context<'_>,
1675 ) -> std::task::Poll<std::io::Result<()>> {
1676 std::pin::Pin::new(&mut self.inner).poll_flush(cx)
1677 }
1678
1679 fn poll_shutdown(
1680 mut self: std::pin::Pin<&mut Self>,
1681 cx: &mut std::task::Context<'_>,
1682 ) -> std::task::Poll<std::io::Result<()>> {
1683 std::pin::Pin::new(&mut self.inner).poll_shutdown(cx)
1684 }
1685}
1686
1687impl HopHandler for H2HopHandler {
1688 fn protocol(&self) -> eggress_uri::ProtocolSpec {
1689 eggress_uri::ProtocolSpec::Http2
1690 }
1691
1692 fn handshake<'a>(
1693 &'a self,
1694 stream: BoxStream,
1695 target: &'a TargetAddr,
1696 hop: &'a eggress_uri::ProxyHopSpec,
1697 hop_index: usize,
1698 ) -> HandshakeFuture<'a> {
1699 let endpoint_host = hop.endpoint.host.clone();
1700 let endpoint_port = hop.endpoint.port;
1701 let auth = hop
1702 .credentials
1703 .as_ref()
1704 .map(|c| (c.username.clone(), c.password.clone()));
1705 let target_clone = target.clone();
1706 let pool_key = eggress_protocol_http::H2PoolKey::with_hop_index(
1707 &endpoint_host,
1708 endpoint_port,
1709 hop.tls,
1710 hop.server_name.as_deref(),
1711 auth.as_ref().map(|(u, p)| (u.as_str(), p.as_str())),
1712 hop_index,
1713 );
1714 Box::pin(async move {
1715 let stream: BoxStream = stream;
1716
1717 let auth_ref = auth.as_ref().map(|(u, p)| (u.as_str(), p.as_str()));
1718 let (send_stream, recv_stream, guard) =
1719 eggress_protocol_http::h2_connect_client_pooled(
1720 stream,
1721 &target_clone,
1722 auth_ref,
1723 &pool_key,
1724 )
1725 .await
1726 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
1727
1728 let h2_write = eggress_protocol_http::H2StreamWrite::new(send_stream);
1729 let h2_read = eggress_protocol_http::H2StreamRead::new(recv_stream);
1730
1731 let pooled = PooledH2Stream {
1732 inner: tokio::io::join(h2_read, h2_write),
1733 _guard: guard,
1734 };
1735 Ok(Box::new(pooled) as BoxStream)
1736 })
1737 }
1738}
1739
1740fn target_to_socks_addr(target: &TargetAddr) -> eggress_protocol_socks::socks5::server::SocksAddr {
1741 use eggress_protocol_socks::socks5::server::SocksAddr;
1742 match &target.host {
1743 TargetHost::Ip(std::net::IpAddr::V4(ip)) => SocksAddr::IPv4(ip.octets(), target.port),
1744 TargetHost::Ip(std::net::IpAddr::V6(ip)) => SocksAddr::IPv6(ip.octets(), target.port),
1745 TargetHost::Domain(d) => SocksAddr::Domain(d.clone(), target.port),
1746 }
1747}