Skip to main content

eggress_server/
execute.rs

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/// Outcome of a session.
30#[derive(Debug)]
31pub enum SessionOutcome {
32    Completed,
33    ClientProtocolError,
34    AuthenticationFailed,
35    HandshakeTimedOut,
36    RouteFailed,
37    RelayFailed,
38    Cancelled,
39}
40
41/// Specific failure category for structured diagnostics and metrics.
42#[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
164/// Execute a session from an accepted connection.
165pub 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
426/// Execute a tunnel session: open route, send success/failure, relay.
427async 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/// Execute an HTTP forward-proxy session with persistent connection support.
527///
528/// Loops over requests on the client connection, forwarding each to the
529/// appropriate upstream. Supports HTTP/1.1 keep-alive semantics: the
530/// connection persists until the client sends `Connection: close` or the
531/// upstream signals close.
532#[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    // Process the first request (already parsed in pending)
551    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                // Determine whether to continue the session
731                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                // Read the next request from the client
740                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                        // Client closed the connection
749                        break;
750                    }
751                    Err(_) => {
752                        // Malformed next request — close with error
753                        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    // Build shared TLS client config for upstream hops
1002    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    // Set up TLS wrapper using system roots by default, or the override if provided
1057    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
1088/// Adapts an origin-form request into the absolute-form request expected by
1089/// pproxy's `httponly` upstream mode. The adapter is deliberately limited to
1090/// the request headers; bodies are passed through unchanged.
1091struct 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
1644/// Wrapper that holds an H2PoolGuard alongside the bidirectional stream,
1645/// ensuring the pooled connection is released back to the pool only when
1646/// the stream is dropped.
1647struct 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}