1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2use std::sync::{Arc, Mutex};
3use std::time::{Duration, Instant};
4
5use arc_swap::ArcSwap;
6#[cfg(feature = "operations")]
7use eggress_admin::{AdminSnapshot, AdminSnapshotProvider, ListenerInfo};
8use eggress_core::listener::{TcpListener, TcpListenerConfig};
9use eggress_core::ProtocolId;
10use eggress_routing::health::HealthManager;
11use eggress_routing::upstream::UpstreamRuntime;
12use eggress_routing::{RouteService, SharedRoutingService};
13use tokio_util::sync::CancellationToken;
14use tokio_util::task::TaskTracker;
15use tracing::Instrument;
16
17use crate::error::RuntimeError;
18use crate::platform::{check_capability, PlatformCapability};
19use crate::snapshot::{compile_runtime_snapshot, CompiledRuntimeSnapshot};
20
21struct ListenerConnectionSlot {
24 active: Arc<AtomicU64>,
25}
26
27impl ListenerConnectionSlot {
28 fn try_acquire(active: &Arc<AtomicU64>, limit: u64) -> Option<Self> {
29 active
30 .fetch_update(Ordering::AcqRel, Ordering::Relaxed, |current| {
31 (current < limit).then_some(current + 1)
32 })
33 .ok()
34 .map(|_| Self {
35 active: active.clone(),
36 })
37 }
38}
39
40impl Drop for ListenerConnectionSlot {
41 fn drop(&mut self) {
42 self.active.fetch_sub(1, Ordering::Release);
43 }
44}
45
46#[derive(Debug)]
48pub enum ReloadResult {
49 Applied { generation: u64, upstreams: usize },
51 Rejected { reason: String },
53 Failed { error: String },
55}
56
57#[cfg(feature = "operations")]
63pub struct RuntimeAdminListenerInfos {
64 state: Arc<RuntimeState>,
65}
66
67#[cfg(feature = "operations")]
68impl AdminSnapshotProvider for RuntimeAdminListenerInfos {
69 fn snapshot(&self) -> AdminSnapshot {
70 let snap = self.state.snapshot.load();
71 let addrs = self
72 .state
73 .listener_addrs
74 .lock()
75 .unwrap_or_else(|e| e.into_inner());
76 let listeners: Vec<ListenerInfo> = snap
77 .listeners
78 .iter()
79 .enumerate()
80 .map(|(idx, lcfg)| {
81 let mode = if lcfg.transparent.as_ref().is_some_and(|t| t.enabled) {
82 Some("transparent".to_string())
83 } else if lcfg.unix.is_some() {
84 Some("unix".to_string())
85 } else {
86 Some("standard".to_string())
87 };
88
89 let capability_status = if lcfg.transparent.as_ref().is_some_and(|t| t.enabled) {
90 let cap = crate::platform::check_capability(
91 crate::platform::PlatformCapability::LinuxOriginalDstIpv4,
92 );
93 Some(cap.to_string())
94 } else {
95 None
96 };
97
98 let original_dst_support = if lcfg.transparent.as_ref().is_some_and(|t| t.enabled) {
99 let cap = crate::platform::check_capability(
100 crate::platform::PlatformCapability::LinuxOriginalDstIpv4,
101 );
102 Some(cap == crate::platform::CapabilityStatus::Available)
103 } else {
104 None
105 };
106
107 let (unix_socket_path, unix_socket_unlink_existing) =
108 if let Some(ref unix_cfg) = lcfg.unix {
109 (
110 Some(unix_cfg.path.display().to_string()),
111 Some(unix_cfg.unlink_existing),
112 )
113 } else {
114 (None, None)
115 };
116
117 ListenerInfo {
118 name: lcfg.name.clone(),
119 bind: lcfg.bind.clone(),
120 local_addr: addrs
121 .get(idx)
122 .and_then(|a| *a)
123 .map(|a| a.to_string())
124 .unwrap_or_default(),
125 protocols: lcfg.protocols.iter().map(|p| p.to_string()).collect(),
126 udp_enabled: lcfg.udp.as_ref().is_some_and(|u| u.enabled),
127 mode,
128 capability_status,
129 original_dst_support,
130 unix_socket_path,
131 unix_socket_unlink_existing,
132 }
133 })
134 .collect();
135 AdminSnapshot {
136 generation: snap.generation,
137 router: snap.router.clone(),
138 pac: snap.admin.as_ref().and_then(|a| a.pac.clone()),
139 static_routes: snap
140 .admin
141 .as_ref()
142 .map(|a| a.static_content.clone())
143 .unwrap_or_default(),
144 listeners,
145 }
146 }
147}
148
149fn classify_listeners(
174 old_listeners: &[eggress_config::compile::ListenerConfig],
175 new_listeners: &[eggress_config::compile::ListenerConfig],
176) -> Result<(), String> {
177 if old_listeners.len() != new_listeners.len() {
178 return Err(format!(
179 "listener count changed ({} -> {}); restart required",
180 old_listeners.len(),
181 new_listeners.len()
182 ));
183 }
184
185 for (old, new) in old_listeners.iter().zip(new_listeners.iter()) {
186 if old.name != new.name {
187 return Err(format!(
188 "listener name changed ('{}' -> '{}'); restart required",
189 old.name, new.name
190 ));
191 }
192 if old.bind != new.bind {
193 return Err(format!(
194 "listener bind address changed for '{}': '{}' -> '{}'; restart required",
195 old.name, old.bind, new.bind
196 ));
197 }
198 match (&old.udp, &new.udp) {
199 (Some(old_udp), Some(new_udp)) => {
200 if old_udp.bind != new_udp.bind {
201 return Err(format!(
202 "UDP bind address changed for '{}': '{}' -> '{}'; restart required",
203 old.name, old_udp.bind, new_udp.bind
204 ));
205 }
206 }
207 (None, Some(new_udp)) => {
208 if !new_udp.bind.ip().is_unspecified() {
209 return Err(format!(
210 "UDP bind address added for '{}': '{}'; restart required",
211 new.name, new_udp.bind
212 ));
213 }
214 }
215 (Some(_old_udp), None) => {}
216 (None, None) => {}
217 }
218
219 match (&old.transparent, &new.transparent) {
220 (Some(old_t), Some(new_t)) => {
221 if old_t.enabled != new_t.enabled {
222 return Err(format!(
223 "transparent config changed for '{}': enabled {} -> {}; restart required",
224 old.name, old_t.enabled, new_t.enabled
225 ));
226 }
227 }
228 (None, Some(new_t)) => {
229 if new_t.enabled {
230 return Err(format!(
231 "transparent proxy enabled for '{}'; restart required",
232 new.name
233 ));
234 }
235 }
236 (Some(_old_t), None) => {}
237 (None, None) => {}
238 }
239
240 match (&old.unix, &new.unix) {
241 (Some(old_u), Some(new_u)) => {
242 if old_u.path != new_u.path {
243 return Err(format!(
244 "unix socket path changed for '{}': '{}' -> '{}'; restart required",
245 old.name,
246 old_u.path.display(),
247 new_u.path.display()
248 ));
249 }
250 }
251 (None, Some(_new_u)) => {
252 return Err(format!(
253 "unix socket added for '{}'; restart required",
254 new.name
255 ));
256 }
257 (Some(_old_u), None) => {
258 return Err(format!(
259 "unix socket removed for '{}'; restart required",
260 old.name
261 ));
262 }
263 (None, None) => {}
264 }
265 }
266
267 Ok(())
268}
269
270struct PreparedListener {
271 name: String,
272 #[allow(dead_code)] bind: String,
274 protocols: Vec<ProtocolId>,
275 listener: TcpListener,
276 local_addr: std::net::SocketAddr,
277 auth: eggress_server::accept::InboundAuthentication,
278 handshake_timeout: Duration,
279 udp: Option<eggress_config::compile::CompiledListenerUdpConfig>,
280 tls: Option<eggress_config::compile::CompiledListenerTlsConfig>,
281 shadowsocks: Option<eggress_config::model::ShadowsocksListenerConfig>,
282 trojan: Option<eggress_config::model::ListenerTrojanConfig>,
283 fixed_target: Option<eggress_core::TargetAddr>,
284 local_bind: Option<String>,
285}
286
287#[cfg(feature = "quic")]
288struct PreparedQuicListener {
289 name: String,
290 protocols: Vec<ProtocolId>,
291 listener: Arc<eggress_transport_quic::QuicListener>,
292 local_addr: std::net::SocketAddr,
293 auth: eggress_server::accept::InboundAuthentication,
294 handshake_timeout: Duration,
295 connection_limit: u64,
296}
297
298#[cfg(feature = "extended")]
299type PreparedShadowsocksUdpRelay = (
300 Arc<tokio::net::UdpSocket>,
301 eggress_udp::standalone_shadowsocks::ShadowsocksStandaloneUdpConfig,
302);
303
304#[cfg(feature = "extended")]
305async fn prepare_shadowsocks_udp_relay(
306 prepared_listener: &PreparedListener,
307 udp_cfg: &eggress_config::compile::CompiledListenerUdpConfig,
308 routing: Arc<dyn RouteService>,
309 state: &RuntimeState,
310) -> Result<PreparedShadowsocksUdpRelay, RuntimeError> {
311 let ss = prepared_listener.shadowsocks.as_ref().ok_or_else(|| {
312 RuntimeError::Other(format!(
313 "listener '{}' shadowsocks_udp mode requires shadowsocks config",
314 prepared_listener.name
315 ))
316 })?;
317 let method =
318 eggress_protocol_shadowsocks::CipherMethod::parse_method(&ss.method).map_err(|e| {
319 RuntimeError::Other(format!(
320 "listener '{}' has invalid shadowsocks method '{}': {}",
321 prepared_listener.name, ss.method, e
322 ))
323 })?;
324 let socket = Arc::new(
325 tokio::net::UdpSocket::bind(udp_cfg.bind)
326 .await
327 .map_err(|e| RuntimeError::ListenerBind {
328 addr: udp_cfg.bind.to_string(),
329 source: e,
330 })?,
331 );
332 let local_addr = socket
333 .local_addr()
334 .map_err(|e| RuntimeError::ListenerBind {
335 addr: udp_cfg.bind.to_string(),
336 source: e,
337 })?;
338 tracing::info!(
339 "shadowsocks UDP relay listening on {local_addr} ({})",
340 prepared_listener.name
341 );
342
343 let relay_config = eggress_udp::standalone_shadowsocks::ShadowsocksStandaloneUdpConfig {
344 routing,
345 udp_metrics: state.udp_metrics.clone(),
346 shadowsocks_metrics: Some(state.shadowsocks_metrics.clone()),
347 limits: eggress_udp::limits::UdpLimits::from_listener_config(
348 udp_cfg.max_associations_global,
349 udp_cfg.max_associations,
350 udp_cfg.max_targets_per_association,
351 udp_cfg.max_datagram_size,
352 udp_cfg.idle_timeout,
353 udp_cfg.client_pin,
354 udp_cfg.target_idle_timeout,
355 ),
356 listener: prepared_listener.name.clone(),
357 generation: state.snapshot.load().generation,
358 method,
359 password: ss.password.clone(),
360 allow_private_egress: udp_cfg.allow_private_egress,
361 };
362
363 Ok((socket, relay_config))
364}
365
366fn compute_advertise_ip(
374 configured_advertise: Option<std::net::IpAddr>,
375 udp_bind_ip: std::net::IpAddr,
376 tcp_peer: std::net::SocketAddr,
377) -> Result<std::net::IpAddr, eggress_udp::error::UdpError> {
378 if let Some(ip) = configured_advertise {
379 return Ok(ip);
380 }
381
382 if !udp_bind_ip.is_unspecified() {
383 return Ok(udp_bind_ip);
384 }
385
386 if tcp_peer.ip().is_loopback() {
387 match tcp_peer {
388 std::net::SocketAddr::V4(_) => {
389 return Ok(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST));
390 }
391 std::net::SocketAddr::V6(_) => {
392 return Ok(std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST));
393 }
394 }
395 }
396
397 Err(eggress_udp::error::UdpError::Other(
398 "UDP relay requires explicit advertise address when bind is unspecified and client is not loopback".to_string()
399 ))
400}
401
402struct RuntimeUdpService {
403 _listener_name: String,
404 udp_config: eggress_config::compile::CompiledListenerUdpConfig,
405 registry: Arc<eggress_udp::registry::UdpAssociationRegistry>,
406 metrics: Arc<dyn eggress_server::SessionMetrics>,
407 udp_metrics: Arc<eggress_udp::metrics::UdpMetrics>,
408 routing: Arc<SharedRoutingService>,
409 udp_tasks: TaskTracker,
410}
411
412impl eggress_server::UdpService for RuntimeUdpService {
413 fn create_association(
414 &self,
415 listener: &str,
416 client_tcp_peer: std::net::SocketAddr,
417 identity: eggress_core::ClientIdentity,
418 generation: u64,
419 ) -> std::pin::Pin<
420 Box<
421 dyn std::future::Future<
422 Output = Result<
423 eggress_server::UdpAssociationHandle,
424 eggress_udp::error::UdpError,
425 >,
426 > + Send
427 + 'static,
428 >,
429 > {
430 let registry = self.registry.clone();
431 let metrics = self.metrics.clone();
432 let udp_metrics = self.udp_metrics.clone();
433 let routing = self.routing.clone();
434 let udp_tasks = self.udp_tasks.clone();
435 let udp_config = self.udp_config.clone();
436 let listener = listener.to_string();
437 Box::pin(async move {
438 let assoc = registry
439 .create_association(&listener, client_tcp_peer, identity, generation)
440 .await?;
441 metrics.record_udp_association_created();
442
443 let relay_socket =
444 std::sync::Arc::new(tokio::net::UdpSocket::bind(udp_config.bind).await?);
445 let local_addr = relay_socket.local_addr()?;
446
447 let advertised_ip =
448 compute_advertise_ip(udp_config.advertise, local_addr.ip(), client_tcp_peer)?;
449 let relay_addr = std::net::SocketAddr::new(advertised_ip, local_addr.port());
450
451 let relay_config = eggress_udp::relay::RelayConfig {
452 routing: routing as Arc<dyn RouteService>,
453 udp_metrics: udp_metrics.clone(),
454 limits: eggress_udp::limits::UdpLimits::from_listener_config(
455 udp_config.max_associations_global,
456 udp_config.max_associations,
457 udp_config.max_targets_per_association,
458 udp_config.max_datagram_size,
459 udp_config.idle_timeout,
460 udp_config.client_pin,
461 udp_config.target_idle_timeout,
462 ),
463 listener: listener.clone(),
464 generation,
465 identity: assoc.meta.identity.clone(),
466 client_tcp_peer,
467 registry: registry.clone(),
468 };
469
470 let relay_assoc = assoc.clone();
471 let relay_cancel = assoc.cancel.clone();
472 let assoc_id = assoc.id;
473 udp_tasks.spawn(async move {
474 let result = eggress_udp::relay::udp_relay_loop(
475 relay_socket,
476 relay_assoc,
477 relay_config,
478 relay_cancel,
479 )
480 .await;
481 if let Err(error) = result {
482 tracing::debug!(
483 %error,
484 association_id = ?assoc_id,
485 "UDP relay ended with error"
486 );
487 }
488 });
489
490 Ok(eggress_server::UdpAssociationHandle {
491 id: assoc.id,
492 relay_addr,
493 cancel: assoc.cancel.clone(),
494 })
495 })
496 }
497
498 fn is_enabled(&self) -> bool {
499 self.udp_config.enabled
500 }
501
502 fn active_count(
503 &self,
504 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = usize> + Send + 'static>> {
505 let registry = self.registry.clone();
506 Box::pin(async move { registry.active_count().await })
507 }
508}
509
510pub struct RuntimeState {
511 pub snapshot: Arc<ArcSwap<CompiledRuntimeSnapshot>>,
512 pub routing: Arc<SharedRoutingService>,
513 pub metrics: Arc<dyn eggress_server::SessionMetrics>,
514 pub readiness: Arc<AtomicBool>,
515 pub start_time: Instant,
516 pub active_connections: Arc<AtomicU64>,
517 pub connection_counter: Arc<AtomicU64>,
518 pub admin_local_addr: Arc<Mutex<Option<std::net::SocketAddr>>>,
519 pub listener_addrs: Arc<Mutex<Vec<Option<std::net::SocketAddr>>>>,
520 pub udp_registry: Arc<eggress_udp::registry::UdpAssociationRegistry>,
521 pub udp_metrics: Arc<eggress_udp::metrics::UdpMetrics>,
522 #[cfg(feature = "extended")]
523 pub shadowsocks_metrics: Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
524 pub udp_tasks: TaskTracker,
525 pub transparent_accepted_total: Arc<AtomicU64>,
526 pub transparent_original_dst_failed_total: Arc<AtomicU64>,
527 #[cfg(feature = "reverse")]
528 pub reverse_registry: Arc<eggress_admin::ReverseRegistry>,
529 #[cfg(feature = "reverse")]
530 pub reverse_metrics: Arc<eggress_protocol_reverse::metrics::ReverseMetrics>,
531}
532
533impl RuntimeState {
534 pub fn generation(&self) -> u64 {
535 self.snapshot.load().generation
536 }
537}
538
539#[allow(dead_code)]
540pub struct ServiceSupervisor {
541 config_path: Option<String>,
542 state: Arc<RuntimeState>,
543 metrics_registry: Arc<eggress_metrics::MetricsRegistry>,
544 cancel: CancellationToken,
545 listener_cancel: CancellationToken,
546 connection_cancel: CancellationToken,
547 health_cancel: CancellationToken,
548 admin_cancel: CancellationToken,
549 health: Option<HealthManager>,
550 tasks: TaskTracker,
551 connection_tasks: TaskTracker,
552 admin_tasks: TaskTracker,
553 shutdown_grace: Duration,
554 rt_config: eggress_config::compile::RuntimeConfig,
555 tls_client_config: Option<std::sync::Arc<rustls::ClientConfig>>,
556 #[cfg(feature = "ssh")]
557 ssh_sessions: Arc<eggress_transport_ssh::SshSessionCache>,
558 compatibility_options: CompatibilityOptions,
559}
560
561#[derive(Debug, Clone, Default)]
564pub struct CompatibilityOptions {
565 pub auth_timeout: Option<Duration>,
566 pub system_proxy: bool,
567 pub debug: bool,
569 pub verbose_level: u8,
572}
573
574impl ServiceSupervisor {
575 pub fn start(config_path: &str) -> Result<Self, RuntimeError> {
576 let (rt_config, warnings) = eggress_config::load_and_validate_with_warnings(config_path)
577 .map_err(|e| RuntimeError::Config(e.to_string()))?;
578
579 for warning in &warnings {
580 tracing::warn!("config security warning: {warning}");
581 }
582
583 Self::init_with_config(
584 rt_config,
585 Some(config_path.to_string()),
586 CompatibilityOptions::default(),
587 )
588 }
589
590 pub fn start_from_config(
597 rt_config: eggress_config::compile::RuntimeConfig,
598 config_path: Option<String>,
599 ) -> Result<Self, RuntimeError> {
600 Self::init_with_config(rt_config, config_path, CompatibilityOptions::default())
601 }
602
603 pub fn start_from_config_with_options(
604 rt_config: eggress_config::compile::RuntimeConfig,
605 config_path: Option<String>,
606 compatibility_options: CompatibilityOptions,
607 ) -> Result<Self, RuntimeError> {
608 Self::init_with_config(rt_config, config_path, compatibility_options)
609 }
610
611 fn init_with_config(
612 rt_config: eggress_config::compile::RuntimeConfig,
613 config_path: Option<String>,
614 compatibility_options: CompatibilityOptions,
615 ) -> Result<Self, RuntimeError> {
616 #[cfg(not(feature = "reverse"))]
617 if !rt_config.reverse_servers.is_empty() || !rt_config.reverse_clients.is_empty() {
618 return Err(RuntimeError::Other(
619 "reverse proxy support not included in this build".to_string(),
620 ));
621 }
622
623 #[cfg(not(feature = "operations"))]
624 if rt_config.admin.as_ref().is_some_and(|a| a.enabled) {
625 return Err(RuntimeError::Other(
626 "admin server support not included in this build; \
627 enable the 'operations' feature or remove [admin] from config"
628 .to_string(),
629 ));
630 }
631
632 for lcfg in &rt_config.listeners {
633 if lcfg.unix.is_none() {
634 let _bind_addr: std::net::SocketAddr =
635 lcfg.bind.parse().map_err(|e| RuntimeError::ListenerBind {
636 addr: lcfg.bind.clone(),
637 source: std::io::Error::new(std::io::ErrorKind::InvalidInput, e),
638 })?;
639 }
640 }
641
642 let udp_metrics = Arc::new(eggress_udp::metrics::UdpMetrics::new());
643 #[cfg(feature = "extended")]
644 let shadowsocks_metrics = Arc::new(eggress_protocol_shadowsocks::ShadowsocksMetrics::new());
645
646 let metrics_registry = Arc::new(eggress_metrics::MetricsRegistry::new());
647 metrics_registry.set_udp_metrics(udp_metrics.clone());
648 #[cfg(feature = "extended")]
649 {
650 metrics_registry.set_shadowsocks_metrics(shadowsocks_metrics.clone());
651 }
652 let metrics: Arc<dyn eggress_server::SessionMetrics> = metrics_registry.clone();
653 let readiness = Arc::new(AtomicBool::new(false));
654
655 let snapshot = compile_runtime_snapshot(&rt_config, None)
656 .map_err(|e| RuntimeError::Config(e.to_string()))?;
657 let snapshot = Arc::new(ArcSwap::from_pointee(snapshot));
658
659 let routing = Arc::new(SharedRoutingService::new_arc(
660 snapshot.load().router.clone(),
661 ));
662
663 let active_connections = Arc::new(AtomicU64::new(0));
664 let connection_counter = Arc::new(AtomicU64::new(1));
665
666 let udp_global_limit = rt_config
667 .listeners
668 .iter()
669 .find_map(|l| l.udp.as_ref().map(|u| u.max_associations_global))
670 .unwrap_or(1024);
671
672 let udp_registry = Arc::new(eggress_udp::registry::UdpAssociationRegistry::new(
673 eggress_udp::limits::UdpLimits {
674 max_associations_global: udp_global_limit,
675 ..Default::default()
676 },
677 ));
678
679 #[cfg(feature = "reverse")]
680 let reverse_metrics = Arc::new(eggress_protocol_reverse::metrics::ReverseMetrics::new());
681 let udp_tasks = TaskTracker::new();
682
683 let state = Arc::new(RuntimeState {
684 snapshot: snapshot.clone(),
685 routing: routing.clone(),
686 metrics: metrics.clone(),
687 readiness,
688 start_time: Instant::now(),
689 active_connections,
690 connection_counter,
691 admin_local_addr: Arc::new(Mutex::new(None)),
692 listener_addrs: Arc::new(Mutex::new(Vec::new())),
693 udp_registry,
694 udp_metrics,
695 #[cfg(feature = "extended")]
696 shadowsocks_metrics,
697 udp_tasks: udp_tasks.clone(),
698 transparent_accepted_total: Arc::new(AtomicU64::new(0)),
699 transparent_original_dst_failed_total: Arc::new(AtomicU64::new(0)),
700 #[cfg(feature = "reverse")]
701 reverse_registry: Arc::new(eggress_admin::ReverseRegistry::new()),
702 #[cfg(feature = "reverse")]
703 reverse_metrics,
704 });
705
706 metrics_registry.set_transparent_counters(
708 state.transparent_accepted_total.clone(),
709 state.transparent_original_dst_failed_total.clone(),
710 );
711
712 let cancel = CancellationToken::new();
713 let listener_cancel = CancellationToken::new();
714 let connection_cancel = CancellationToken::new();
715 let health_cancel = CancellationToken::new();
716 let admin_cancel = CancellationToken::new();
717 #[cfg(feature = "ssh")]
718 let ssh_sessions = Arc::new(eggress_transport_ssh::SshSessionCache::new());
719
720 let tasks = TaskTracker::new();
721 let connection_tasks = TaskTracker::new();
722
723 let mut health: Option<HealthManager> = None;
724
725 {
726 let upstream_runtimes: Vec<Arc<UpstreamRuntime>> =
727 snapshot.load().upstreams.values().cloned().collect();
728
729 if !upstream_runtimes.is_empty() {
730 let hm = HealthManager::new(health_cancel.clone());
731 health = Some(hm);
732 }
733 }
734
735 let shutdown_grace = rt_config.process.shutdown_grace;
736
737 Ok(ServiceSupervisor {
738 config_path,
739 state,
740 metrics_registry,
741 cancel,
742 listener_cancel,
743 connection_cancel,
744 health_cancel,
745 admin_cancel,
746 health,
747 tasks,
748 connection_tasks,
749 admin_tasks: TaskTracker::new(),
750 shutdown_grace,
751 rt_config,
752 tls_client_config: None,
753 #[cfg(feature = "ssh")]
754 ssh_sessions,
755 compatibility_options,
756 })
757 }
758
759 pub fn state(&self) -> &Arc<RuntimeState> {
760 &self.state
761 }
762
763 pub fn shutdown_token(&self) -> CancellationToken {
764 self.cancel.clone()
765 }
766
767 #[allow(dead_code)]
770 pub fn with_tls_client_config(mut self, config: std::sync::Arc<rustls::ClientConfig>) -> Self {
771 self.tls_client_config = Some(config);
772 self
773 }
774
775 fn classify_reload(
784 &self,
785 new_config: &eggress_config::compile::RuntimeConfig,
786 ) -> Result<(), String> {
787 classify_listeners(&self.rt_config.listeners, &new_config.listeners)
788 }
789
790 pub fn reload_config(&mut self) -> ReloadResult {
797 let config_path = match self.config_path {
798 Some(ref p) => p.clone(),
799 None => {
800 return ReloadResult::Rejected {
801 reason: "no config file path available for reload".to_string(),
802 };
803 }
804 };
805 let new_rt_config = match eggress_config::compile::load_and_compile(&config_path) {
806 Ok(c) => c,
807 Err(e) => {
808 return ReloadResult::Failed {
809 error: format!("config load: {e}"),
810 };
811 }
812 };
813
814 if let Err(reason) = self.classify_reload(&new_rt_config) {
815 return ReloadResult::Rejected { reason };
816 }
817
818 let prev_snapshot = self.state.snapshot.load();
819 let prev_ref: Option<&CompiledRuntimeSnapshot> = Some(&prev_snapshot);
820 let new_snapshot = match compile_runtime_snapshot(&new_rt_config, prev_ref) {
821 Ok(s) => s,
822 Err(e) => {
823 return ReloadResult::Failed {
824 error: format!("snapshot build: {e}"),
825 };
826 }
827 };
828
829 let upstream_count = new_snapshot.upstreams.len();
830 let gen = new_snapshot.generation;
831
832 let new_snapshot = Arc::new(new_snapshot);
837 self.state.snapshot.store(new_snapshot.clone());
838 self.state.routing.swap_arc(new_snapshot.router.clone());
839
840 self.rt_config = new_rt_config;
841
842 ReloadResult::Applied {
843 generation: gen,
844 upstreams: upstream_count,
845 }
846 }
847
848 pub fn run(&mut self) -> Result<(), RuntimeError> {
849 #[allow(unused_variables)]
850 let config_path = self.config_path.clone().unwrap_or_default();
851 let routing = self.state.routing.clone();
852 let listener_cancel = self.listener_cancel.clone();
853 let connection_cancel = self.connection_cancel.clone();
854 let health_cancel = self.health_cancel.clone();
855 let admin_cancel = self.admin_cancel.clone();
856 let cancel = self.cancel.clone();
857 #[allow(unused_variables)]
858 let metrics = self.state.metrics.clone();
859 let readiness = self.state.readiness.clone();
860 #[cfg(feature = "operations")]
861 let admin_state_ref = self.state.clone();
862 let active_connections = self.state.active_connections.clone();
863 let shutdown_grace = self.shutdown_grace;
864 let tasks = self.tasks.clone();
865 let connection_tasks = self.connection_tasks.clone();
866 let admin_tasks = self.admin_tasks.clone();
867 let health = std::sync::Arc::new(std::sync::Mutex::new(self.health.take()));
868 #[allow(unused_variables)]
869 let health_clone = health.clone();
870 let health_for_run = health.clone();
871 let snapshot = self.state.snapshot.clone();
872 let state_ref = self.state.clone();
873 let rt_config = self.rt_config.clone();
874 let tls_client_config = self.tls_client_config.clone();
875 let compatibility_options = self.compatibility_options.clone();
876 #[cfg(feature = "ssh")]
877 let ssh_sessions = self.ssh_sessions.clone();
878
879 let handshake_timeout = rt_config.timeouts.handshake;
880 let connect_timeout = rt_config.timeouts.connect;
881
882 #[cfg(feature = "operations")]
883 let listener_infos_provider: Arc<RuntimeAdminListenerInfos> =
884 Arc::new(RuntimeAdminListenerInfos {
885 state: admin_state_ref.clone(),
886 });
887
888 #[cfg(feature = "operations")]
889 let metrics_registry_for_admin = self.metrics_registry.clone();
890
891 let run_async = async move {
892 #[cfg(feature = "operations")]
893 let mut compatibility_system_proxy: Option<
894 eggress_system_proxy::AppliedProxy,
895 > = None;
896
897 #[cfg(feature = "operations")]
898 let metrics_registry = metrics_registry_for_admin;
899 {
901 let mut guard = health_for_run.lock().unwrap_or_else(|e| e.into_inner());
902 if let Some(ref mut hm) = *guard {
903 let upstream_runtimes: Vec<Arc<UpstreamRuntime>> =
904 snapshot.load().upstreams.values().cloned().collect();
905 if !upstream_runtimes.is_empty() {
906 hm.start_probes(&upstream_runtimes);
907 }
908 }
909 }
910
911 let current_snapshot = snapshot.load();
912 let listener_configs = current_snapshot.listeners.clone();
913 #[cfg(feature = "operations")]
914 let admin_config = current_snapshot.admin.clone();
915 drop(current_snapshot);
916
917 if listener_configs.is_empty() {
918 tracing::warn!("no listeners configured; the proxy will not accept connections");
919 }
920
921 let mut prepared = Vec::new();
922 #[cfg(feature = "quic")]
923 let mut prepared_quic = Vec::<PreparedQuicListener>::new();
924 let compatibility_auth_reuse = compatibility_options
925 .auth_timeout
926 .map(eggress_server::accept::AuthReuseCache::new)
927 .map(Arc::new);
928 #[cfg(unix)]
929 let mut unix_listener_args = Vec::new();
930 let mut transparent_listener_args = Vec::new();
931
932 for lcfg in &listener_configs {
933 let protocols: Vec<ProtocolId> = lcfg.protocols.to_vec();
934
935 let auth = match &lcfg.auth {
936 Some(auth_cfg) => {
937 if auth_cfg.auth_type == "password" {
938 let username = auth_cfg.username.clone().unwrap_or_default();
939 let password = auth_cfg.password.clone().unwrap_or_default();
940 if let Some(reuse) = compatibility_auth_reuse.clone() {
941 eggress_server::accept::InboundAuthentication::UsernamePasswordWithReuse {
942 username,
943 password,
944 reuse,
945 }
946 } else {
947 eggress_server::accept::InboundAuthentication::UsernamePassword {
948 username,
949 password,
950 }
951 }
952 } else {
953 eggress_server::accept::InboundAuthentication::None
954 }
955 }
956 None => eggress_server::accept::InboundAuthentication::None,
957 };
958
959 let connection_limit = lcfg.connection_limit.unwrap_or(1024) as usize;
960
961 #[allow(unused_variables)]
963 if let Some(ref unix_cfg) = lcfg.unix {
964 #[cfg(unix)]
965 {
966 match eggress_server::listener::unix::create_unix_listener(
967 &eggress_server::listener::unix::UnixListenerConfig::from_compiled(
968 &unix_cfg.path,
969 unix_cfg.unlink_existing,
970 Some(unix_cfg.mode),
971 ),
972 ) {
973 Ok(unix_listener) => {
974 tracing::info!(
975 "unix socket listener created at {} ({})",
976 unix_cfg.path.display(),
977 lcfg.name
978 );
979 unix_listener_args.push((
980 lcfg.name.clone(),
981 unix_listener,
982 protocols,
983 auth,
984 handshake_timeout,
985 connection_limit as u64,
986 lcfg.tls.clone(),
987 lcfg.shadowsocks.clone(),
988 lcfg.trojan.clone(),
989 lcfg.udp.clone(),
990 ));
991 continue;
992 }
993 Err(e) => {
994 tracing::error!(
995 "failed to bind unix socket at {} for listener '{}': {e}",
996 unix_cfg.path.display(),
997 lcfg.name
998 );
999 continue;
1000 }
1001 }
1002 }
1003 #[cfg(not(unix))]
1004 {
1005 tracing::error!(
1006 "unix socket listener '{}' skipped: not supported on this platform",
1007 lcfg.name
1008 );
1009 continue;
1010 }
1011 }
1012
1013 if let Some(ref transparent_cfg) = lcfg.transparent {
1015 if transparent_cfg.enabled {
1016 let capability = check_capability(PlatformCapability::LinuxOriginalDstIpv4);
1017 if capability != crate::platform::CapabilityStatus::Available {
1018 #[cfg(feature = "operations")]
1019 state_ref.metrics.record_platform_capability_check_failure();
1020 let _cap_span = tracing::info_span!(
1021 "capability_check_failed",
1022 capability = %PlatformCapability::LinuxOriginalDstIpv4,
1023 status = %capability,
1024 listener = %lcfg.name,
1025 );
1026 tracing::warn!(
1027 "transparent proxy not available for listener '{}' ({}); \
1028 falling back to normal TCP listener",
1029 lcfg.name,
1030 capability
1031 );
1032 } else {
1033 let bind_addr: std::net::SocketAddr =
1034 lcfg.bind.parse().map_err(|e| RuntimeError::ListenerBind {
1035 addr: lcfg.bind.clone(),
1036 source: std::io::Error::new(
1037 std::io::ErrorKind::InvalidInput,
1038 e,
1039 ),
1040 })?;
1041
1042 let transparent_listener =
1043 eggress_server::listener::transparent::TransparentListener::bind(
1044 &bind_addr.to_string(),
1045 )
1046 .await
1047 .map_err(|e| {
1048 RuntimeError::ListenerBind {
1049 addr: lcfg.bind.clone(),
1050 source: e,
1051 }
1052 })?;
1053
1054 let local_addr = transparent_listener.local_addr().map_err(|e| {
1055 RuntimeError::ListenerBind {
1056 addr: lcfg.bind.clone(),
1057 source: e,
1058 }
1059 })?;
1060
1061 tracing::info!(
1062 "transparent TCP listener listening on {local_addr} ({})",
1063 lcfg.name
1064 );
1065
1066 transparent_listener_args.push((
1067 lcfg.name.clone(),
1068 transparent_listener,
1069 protocols,
1070 auth,
1071 handshake_timeout,
1072 connection_limit as u64,
1073 lcfg.tls.clone(),
1074 lcfg.shadowsocks.clone(),
1075 lcfg.trojan.clone(),
1076 lcfg.udp.clone(),
1077 ));
1078 continue;
1079 }
1080 }
1081 }
1082
1083 #[cfg(feature = "quic")]
1084 if protocols.contains(&ProtocolId::Quic) || protocols.contains(&ProtocolId::Http3) {
1085 let tls = lcfg.tls.clone().ok_or_else(|| {
1086 RuntimeError::Other(format!(
1087 "QUIC/HTTP3 listener '{}' requires certificate and key material",
1088 lcfg.name
1089 ))
1090 })?;
1091 let bind_addr: std::net::SocketAddr =
1092 lcfg.bind.parse().map_err(|e| RuntimeError::ListenerBind {
1093 addr: lcfg.bind.clone(),
1094 source: std::io::Error::new(std::io::ErrorKind::InvalidInput, e),
1095 })?;
1096 let listener = eggress_transport_quic::QuicListener::bind(
1097 bind_addr,
1098 eggress_transport_quic::QuicServerConfig {
1099 certificate_pem: tls.cert_pem.clone(),
1100 private_key_pem: tls.key_pem.clone(),
1101 idle_timeout: Duration::from_secs(60),
1102 max_concurrent_streams: lcfg.connection_limit.unwrap_or(1024).max(1),
1103 alpn_protocols: if protocols.contains(&ProtocolId::Http3) {
1104 vec![b"h3".to_vec()]
1105 } else {
1106 Vec::new()
1107 },
1108 },
1109 )
1110 .await
1111 .map_err(|e| RuntimeError::ListenerBind {
1112 addr: lcfg.bind.clone(),
1113 source: std::io::Error::other(e.to_string()),
1114 })?;
1115 let local_addr =
1116 listener
1117 .local_addr()
1118 .map_err(|e| RuntimeError::ListenerBind {
1119 addr: lcfg.bind.clone(),
1120 source: std::io::Error::other(e.to_string()),
1121 })?;
1122 tracing::info!("QUIC listening on {local_addr} ({})", lcfg.name);
1123 prepared_quic.push(PreparedQuicListener {
1124 name: lcfg.name.clone(),
1125 protocols,
1126 listener,
1127 local_addr,
1128 auth,
1129 handshake_timeout,
1130 connection_limit: lcfg.connection_limit.unwrap_or(1024) as u64,
1131 });
1132 continue;
1133 }
1134
1135 let bind_addr: std::net::SocketAddr =
1137 lcfg.bind.parse().map_err(|e| RuntimeError::ListenerBind {
1138 addr: lcfg.bind.clone(),
1139 source: std::io::Error::new(std::io::ErrorKind::InvalidInput, e),
1140 })?;
1141
1142 let config = TcpListenerConfig {
1143 bind_addr,
1144 protocols: protocols.clone(),
1145 auth_required: false,
1146 handshake_timeout,
1147 connection_limit,
1148 };
1149
1150 let listener = TcpListener::new_with_reuse_port(
1151 &config,
1152 listener_cancel.clone(),
1153 lcfg.reuse_port.unwrap_or(false),
1154 )
1155 .await
1156 .map_err(|e| RuntimeError::ListenerBind {
1157 addr: lcfg.bind.clone(),
1158 source: e,
1159 })?;
1160 let local_addr = listener
1161 .local_addr()
1162 .map_err(|e| RuntimeError::ListenerBind {
1163 addr: lcfg.bind.clone(),
1164 source: e,
1165 })?;
1166 tracing::info!("listening on {local_addr} ({})", lcfg.name);
1167
1168 prepared.push(PreparedListener {
1169 name: lcfg.name.clone(),
1170 bind: lcfg.bind.clone(),
1171 protocols,
1172 listener,
1173 local_addr,
1174 auth,
1175 handshake_timeout,
1176 udp: lcfg.udp.clone(),
1177 tls: lcfg.tls.clone(),
1178 shadowsocks: lcfg.shadowsocks.clone(),
1179 trojan: lcfg.trojan.clone(),
1180 fixed_target: lcfg.fixed_target.clone(),
1181 local_bind: lcfg.local_bind.clone(),
1182 });
1183 }
1184
1185 #[cfg(feature = "operations")]
1186 {
1187 let listener_infos: Vec<eggress_admin::ListenerInfo> = prepared
1188 .iter()
1189 .map(|p| eggress_admin::ListenerInfo {
1190 name: p.name.clone(),
1191 bind: p.bind.clone(),
1192 local_addr: p.local_addr.to_string(),
1193 protocols: p.protocols.iter().map(|p| p.to_string()).collect(),
1194 udp_enabled: p.udp.as_ref().is_some_and(|u| u.enabled),
1195 mode: Some("standard".to_string()),
1196 capability_status: None,
1197 original_dst_support: None,
1198 unix_socket_path: None,
1199 unix_socket_unlink_existing: None,
1200 })
1201 .collect();
1202 drop(listener_infos);
1203 }
1204
1205 {
1208 let mut addr_map: std::collections::HashMap<String, Option<std::net::SocketAddr>> =
1209 std::collections::HashMap::new();
1210 for p in &prepared {
1211 addr_map.insert(p.name.clone(), Some(p.local_addr));
1212 }
1213 #[cfg(feature = "quic")]
1214 for p in &prepared_quic {
1215 addr_map.insert(p.name.clone(), Some(p.local_addr));
1216 }
1217 for (name, transparent_listener, _, _, _, _, _, _, _, _) in
1218 &transparent_listener_args
1219 {
1220 let addr = transparent_listener.local_addr().ok();
1221 addr_map.insert(name.clone(), addr);
1222 }
1223 #[cfg(unix)]
1224 for (name, _, _, _, _, _, _, _, _, _) in &unix_listener_args {
1225 addr_map.insert(name.clone(), None);
1227 }
1228 let addrs: Vec<Option<std::net::SocketAddr>> = listener_configs
1229 .iter()
1230 .map(|lcfg| addr_map.get(&lcfg.name).copied().flatten())
1231 .collect();
1232 *state_ref
1233 .listener_addrs
1234 .lock()
1235 .unwrap_or_else(|e| e.into_inner()) = addrs;
1236 }
1237
1238 #[cfg(feature = "extended")]
1239 let mut shadowsocks_udp_relays = Vec::new();
1240 let mut echo_udp_relays = Vec::new();
1241
1242 for prepared_listener in &prepared {
1243 if let Some(ref udp_cfg) = prepared_listener.udp {
1244 #[cfg(feature = "extended")]
1245 if udp_cfg.mode == eggress_udp::UdpMode::ShadowsocksUdp {
1246 shadowsocks_udp_relays.push(
1247 prepare_shadowsocks_udp_relay(
1248 prepared_listener,
1249 udp_cfg,
1250 routing.clone(),
1251 &state_ref,
1252 )
1253 .await?,
1254 );
1255 }
1256 if udp_cfg.mode == eggress_udp::UdpMode::Echo {
1257 let socket =
1258 Arc::new(tokio::net::UdpSocket::bind(udp_cfg.bind).await.map_err(
1259 |e| RuntimeError::ListenerBind {
1260 addr: udp_cfg.bind.to_string(),
1261 source: e,
1262 },
1263 )?);
1264 echo_udp_relays.push((prepared_listener.name.clone(), socket, None));
1265 } else if udp_cfg.mode == eggress_udp::UdpMode::FixedTarget {
1266 let socket =
1267 Arc::new(tokio::net::UdpSocket::bind(udp_cfg.bind).await.map_err(
1268 |e| RuntimeError::ListenerBind {
1269 addr: udp_cfg.bind.to_string(),
1270 source: e,
1271 },
1272 )?);
1273 echo_udp_relays.push((
1274 prepared_listener.name.clone(),
1275 socket,
1276 udp_cfg.fixed_target.clone(),
1277 ));
1278 }
1279 }
1280 }
1281
1282 #[cfg(feature = "extended")]
1283 for (socket, relay_config) in shadowsocks_udp_relays {
1284 let relay_cancel = cancel.clone();
1285 tasks.spawn(async move {
1286 let result =
1287 eggress_udp::standalone_shadowsocks::shadowsocks_standalone_udp_relay(
1288 socket,
1289 relay_config,
1290 relay_cancel,
1291 )
1292 .await;
1293 if let Err(error) = result {
1294 tracing::debug!(
1295 %error,
1296 "Shadowsocks UDP relay ended with error"
1297 );
1298 }
1299 });
1300 }
1301
1302 for (listener_name, socket, fixed_target) in echo_udp_relays {
1303 let relay_cancel = cancel.clone();
1304 let max_size = listener_configs
1305 .iter()
1306 .find(|l| l.name == listener_name)
1307 .and_then(|l| l.udp.as_ref())
1308 .map(|u| u.max_datagram_size)
1309 .unwrap_or(65535);
1310 tasks.spawn(async move {
1311 let mut buf = vec![0u8; max_size];
1312 let target_socket = if let Some(target) = fixed_target {
1313 let addr = match target.host {
1314 eggress_core::TargetHost::Ip(ip) => std::net::SocketAddr::new(ip, target.port),
1315 eggress_core::TargetHost::Domain(domain) => match tokio::net::lookup_host((domain.as_str(), target.port)).await {
1316 Ok(mut addrs) => match addrs.next() { Some(addr) => addr, None => return },
1317 Err(_) => return,
1318 },
1319 };
1320 let target_socket = match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
1321 Ok(s) => s,
1322 Err(_) => return,
1323 };
1324 if target_socket.connect(addr).await.is_err() { return; }
1325 Some(target_socket)
1326 } else { None };
1327 let mut response = vec![0u8; max_size];
1328 loop {
1329 tokio::select! {
1330 result = socket.recv_from(&mut buf) => match result {
1331 Ok((n, peer)) => {
1332 if let Some(ref target_socket) = target_socket {
1333 if target_socket.send(&buf[..n]).await.is_ok() {
1334 if let Ok(m) = target_socket.recv(&mut response).await { let _ = socket.send_to(&response[..m], peer).await; }
1335 }
1336 } else { let _ = socket.send_to(&buf[..n], peer).await; }
1337 }
1338 Err(_) => break,
1339 },
1340 _ = relay_cancel.cancelled() => break,
1341 }
1342 }
1343 });
1344 }
1345
1346 for (
1348 listener_name,
1349 transparent_listener,
1350 protocols,
1351 auth,
1352 hs_timeout,
1353 connection_limit,
1354 tls_cfg,
1355 ss_cfg,
1356 trojan_cfg,
1357 udp_cfg,
1358 ) in transparent_listener_args
1359 {
1360 let routing = routing.clone();
1361 let state = state_ref.clone();
1362 let conn_tasks = connection_tasks.clone();
1363 let conn_cancel = connection_cancel.clone();
1364 let tls_client_config = tls_client_config.clone();
1365 let listener_cancel = listener_cancel.clone();
1366
1367 #[cfg(feature = "ssh")]
1368 let listener_ssh_sessions = ssh_sessions.clone();
1369
1370 tasks.spawn(async move {
1371 let proto_slice: Arc<[ProtocolId]> = protocols.clone().into();
1372 let transparent_listener_inner = transparent_listener.inner();
1373 let listener_active = Arc::new(AtomicU64::new(0));
1374
1375 let transparent_accepted = state.transparent_accepted_total.clone();
1376 let transparent_dst_failed =
1377 state.transparent_original_dst_failed_total.clone();
1378
1379 loop {
1380 let accept_result = tokio::select! {
1381 result = transparent_listener_inner.accept() => result,
1382 _ = listener_cancel.cancelled() => {
1383 break;
1384 }
1385 };
1386
1387 let (stream, _peer) = match accept_result {
1388 Ok(s) => s,
1389 Err(e) => {
1390 if e.to_string().contains("listener cancelled") {
1391 break;
1392 }
1393 tracing::error!(
1394 "transparent accept error on '{}': {e}",
1395 listener_name
1396 );
1397 continue;
1398 }
1399 };
1400
1401 let connection_slot = match ListenerConnectionSlot::try_acquire(
1402 &listener_active,
1403 connection_limit,
1404 ) {
1405 Some(slot) => slot,
1406 None => {
1407 tracing::debug!(
1408 listener = %listener_name,
1409 limit = connection_limit,
1410 "dropping transparent connection: connection limit reached"
1411 );
1412 drop(stream);
1413 continue;
1414 }
1415 };
1416
1417 transparent_accepted.fetch_add(1, Ordering::Relaxed);
1418
1419 let original_dst =
1420 match eggress_server::listener::transparent::get_original_destination(&stream) {
1421 Ok(addr) => addr,
1422 Err(e) => {
1423 drop(connection_slot);
1424 transparent_dst_failed.fetch_add(1, Ordering::Relaxed);
1425 let _span = tracing::info_span!(
1426 "transparent_original_dst_failed",
1427 listener = %listener_name,
1428 error = %e,
1429 );
1430 tracing::warn!(
1431 "failed to get original destination for transparent connection on '{}': {e}",
1432 listener_name
1433 );
1434 continue;
1435 }
1436 };
1437
1438 let peer = stream
1439 .peer_addr()
1440 .unwrap_or_else(|_| {
1441 std::net::SocketAddr::new(
1442 std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
1443 0,
1444 )
1445 });
1446
1447 let routing = routing.clone();
1448 let tls_client_config = tls_client_config.clone();
1449 let listener_str = listener_name.clone();
1450 let conn_id = state
1451 .connection_counter
1452 .fetch_add(1, Ordering::Relaxed);
1453 let conn_protocols = proto_slice.clone();
1454 let conn_auth = auth.clone();
1455 let conn_metrics = state.metrics.clone();
1456 #[cfg(feature = "extended")]
1457 let conn_ss_metrics = state.shadowsocks_metrics.clone();
1458 let active = state.active_connections.clone();
1459 let conn_cancel = conn_cancel.child_token();
1460 let generation = state.snapshot.load().generation;
1461 let tls_config = tls_cfg.clone();
1462 let ss_config = ss_cfg.clone();
1463 let trojan_config = trojan_cfg.clone();
1464
1465 let udp_svc = udp_cfg.as_ref().map(|udp_config| {
1466 Arc::new(RuntimeUdpService {
1467 _listener_name: listener_name.clone(),
1468 udp_config: udp_config.clone(),
1469 registry: state.udp_registry.clone(),
1470 metrics: state.metrics.clone(),
1471 udp_metrics: state.udp_metrics.clone(),
1472 routing: routing.clone(),
1473 udp_tasks: state.udp_tasks.clone(),
1474 }) as Arc<dyn eggress_server::UdpService>
1475 });
1476
1477 active.fetch_add(1, Ordering::Relaxed);
1478
1479 #[cfg(feature = "ssh")]
1480 let conn_ssh_sessions = listener_ssh_sessions.clone();
1481 conn_tasks.spawn(async move {
1482 let _connection_slot = connection_slot;
1483 let started = std::time::Instant::now();
1484
1485 let stream: eggress_core::BoxStream =
1486 if let Some(ref tls_cfg) = tls_config {
1487 let server_config = match eggress_transport_tls::TlsServerConfigBuilder::new()
1488 .with_certificate_pem(&tls_cfg.cert_pem)
1489 .and_then(|b| b.with_key_pem(&tls_cfg.key_pem))
1490 .and_then(|b| {
1491 let b = if tls_cfg.alpn.is_empty() { b } else { b.with_alpn(tls_cfg.alpn.clone()) };
1492 b.build()
1493 }) {
1494 Ok(c) => c,
1495 Err(e) => {
1496 tracing::error!(%peer, "TLS config error: {e}");
1497 active.fetch_sub(1, Ordering::Relaxed);
1498 return;
1499 }
1500 };
1501 match eggress_transport_tls::tls_accept(Box::new(stream), server_config).await {
1502 Ok(s) => s,
1503 Err(e) => {
1504 tracing::debug!(%peer, "TLS accept failed: {e}");
1505 active.fetch_sub(1, Ordering::Relaxed);
1506 return;
1507 }
1508 }
1509 } else {
1510 Box::new(stream)
1511 };
1512
1513 let config = eggress_server::ConnectionConfig {
1514 routing: routing as Arc<dyn RouteService>,
1515 context: eggress_server::ConnectionContext {
1516 source: Some(peer),
1517 listener: listener_str.clone(),
1518 generation,
1519 },
1520 handshake_timeout: hs_timeout,
1521 connect_timeout,
1522 protocols: conn_protocols,
1523 authentication: conn_auth,
1524 metrics: Some(conn_metrics),
1525 udp: udp_svc,
1526 tls_client_config,
1527 shadowsocks: ss_config.map(
1528 |ss| eggress_server::accept::InboundShadowsocksConfig {
1529 method: ss.method,
1530 password: ss.password,
1531 #[cfg(feature = "pproxy-legacy")]
1532 auth_prefix: ss.auth_prefix.map(String::into_bytes),
1533 #[cfg(feature = "pproxy-legacy")]
1534 plugins: ss.plugins,
1535 },
1536 ),
1537 #[cfg(feature = "extended")]
1538 shadowsocks_metrics: Some(conn_ss_metrics),
1539 #[cfg(not(feature = "extended"))]
1540 shadowsocks_metrics: None,
1541 trojan: trojan_config.map(
1542 |t| eggress_server::accept::InboundTrojanConfig {
1543 password: t.password,
1544 fallback: t.fallback,
1545 },
1546 ),
1547 fixed_target: None,
1548 local_bind: None,
1549 #[cfg(feature = "ssh")]
1550 ssh_sessions: Some(conn_ssh_sessions),
1551 };
1552
1553 let report = tokio::select! {
1554 report = eggress_server::serve_connection(stream, config)
1555 .instrument(tracing::info_span!(
1556 "conn",
1557 id = conn_id,
1558 peer = %peer,
1559 original_dst = %original_dst,
1560 listener_type = "transparent",
1561 listener = %listener_str,
1562 )) => {
1563 report
1564 }
1565 _ = conn_cancel.cancelled() => {
1566 eggress_server::SessionReport::cancelled(
1567 None,
1568 None,
1569 String::new(),
1570 )
1571 }
1572 };
1573
1574 active.fetch_sub(1, Ordering::Relaxed);
1575
1576 tracing::info!(
1577 protocol = ?report.protocol,
1578 target = ?report.target,
1579 original_dst = %original_dst,
1580 route = %report.route,
1581 outcome = ?report.outcome,
1582 bytes_upstream = report.bytes_upstream,
1583 bytes_downstream = report.bytes_downstream,
1584 duration_ms = started.elapsed().as_millis() as u64,
1585 "transparent connection completed",
1586 );
1587 });
1588 }
1589 });
1590 }
1591
1592 #[cfg(unix)]
1594 for (
1595 listener_name,
1596 unix_listener,
1597 protocols,
1598 auth,
1599 hs_timeout,
1600 connection_limit,
1601 tls_cfg,
1602 ss_cfg,
1603 trojan_cfg,
1604 udp_cfg,
1605 ) in unix_listener_args
1606 {
1607 let routing = routing.clone();
1608 let state = state_ref.clone();
1609 let conn_tasks = connection_tasks.clone();
1610 let conn_cancel = connection_cancel.clone();
1611 let tls_client_config = tls_client_config.clone();
1612 let listener_cancel = listener_cancel.clone();
1613
1614 let socket_path = unix_listener.path().display().to_string();
1615
1616 #[cfg(feature = "ssh")]
1617 let listener_ssh_sessions = ssh_sessions.clone();
1618 tasks.spawn(async move {
1619 let proto_slice: Arc<[ProtocolId]> = protocols.clone().into();
1620 let listener_active = Arc::new(AtomicU64::new(0));
1621
1622 let _accept_loop_span = tracing::info_span!(
1623 "unix_accept_loop",
1624 listener = %listener_name,
1625 socket_path = %socket_path,
1626 );
1627
1628 loop {
1629 let (stream, _peer_addr) = tokio::select! {
1630 result = unix_listener.accept() => match result {
1631 Ok(r) => r,
1632 Err(e) => {
1633 tracing::error!("unix accept error on '{}': {e}", listener_name);
1634 continue;
1635 }
1636 },
1637 _ = listener_cancel.cancelled() => {
1638 break;
1639 }
1640 };
1641
1642 let connection_slot = match ListenerConnectionSlot::try_acquire(
1643 &listener_active,
1644 connection_limit,
1645 ) {
1646 Some(slot) => slot,
1647 None => {
1648 tracing::debug!(
1649 listener = %listener_name,
1650 limit = connection_limit,
1651 "dropping Unix connection: connection limit reached"
1652 );
1653 drop(stream);
1654 continue;
1655 }
1656 };
1657
1658 state.metrics.record_unix_listener_connection_accepted();
1659
1660 let routing = routing.clone();
1661 let tls_client_config = tls_client_config.clone();
1662 let listener_str = listener_name.clone();
1663 let conn_id =
1664 state.connection_counter.fetch_add(1, Ordering::Relaxed);
1665 let conn_protocols = proto_slice.clone();
1666 let conn_auth = auth.clone();
1667 let conn_metrics = state.metrics.clone();
1668 #[cfg(feature = "extended")]
1669 let conn_ss_metrics = state.shadowsocks_metrics.clone();
1670 let active = state.active_connections.clone();
1671 let conn_cancel = conn_cancel.child_token();
1672 let generation = state.snapshot.load().generation;
1673
1674 let tls_config = tls_cfg.clone();
1675 let ss_config = ss_cfg.clone();
1676 let trojan_config = trojan_cfg.clone();
1677 let socket_path_clone = socket_path.clone();
1678 let listener_str_for_span = listener_str.clone();
1679
1680 let udp_svc = udp_cfg.as_ref().map(|udp_config| {
1681 Arc::new(RuntimeUdpService {
1682 _listener_name: listener_name.clone(),
1683 udp_config: udp_config.clone(),
1684 registry: state.udp_registry.clone(),
1685 metrics: state.metrics.clone(),
1686 udp_metrics: state.udp_metrics.clone(),
1687 routing: routing.clone(),
1688 udp_tasks: state.udp_tasks.clone(),
1689 }) as Arc<dyn eggress_server::UdpService>
1690 });
1691
1692 active.fetch_add(1, Ordering::Relaxed);
1693
1694 #[cfg(feature = "ssh")]
1695 let conn_ssh_sessions = listener_ssh_sessions.clone();
1696 conn_tasks.spawn(async move {
1697 let _connection_slot = connection_slot;
1698 let started = std::time::Instant::now();
1699
1700 let stream: eggress_core::BoxStream =
1701 if let Some(ref tls_cfg) = tls_config {
1702 let server_config = match eggress_transport_tls::TlsServerConfigBuilder::new()
1703 .with_certificate_pem(&tls_cfg.cert_pem)
1704 .and_then(|b| b.with_key_pem(&tls_cfg.key_pem))
1705 .and_then(|b| {
1706 let b = if tls_cfg.alpn.is_empty() { b } else { b.with_alpn(tls_cfg.alpn.clone()) };
1707 b.build()
1708 }) {
1709 Ok(c) => c,
1710 Err(e) => {
1711 tracing::error!("TLS config error for unix connection: {e}");
1712 active.fetch_sub(1, Ordering::Relaxed);
1713 return;
1714 }
1715 };
1716 match eggress_transport_tls::tls_accept(Box::new(stream), server_config).await {
1717 Ok(s) => s,
1718 Err(e) => {
1719 tracing::debug!("TLS accept failed for unix connection: {e}");
1720 active.fetch_sub(1, Ordering::Relaxed);
1721 return;
1722 }
1723 }
1724 } else {
1725 Box::new(stream)
1726 };
1727
1728 let peer = std::net::SocketAddr::new(
1729 std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
1730 0,
1731 );
1732
1733 let config = eggress_server::ConnectionConfig {
1734 routing: routing as Arc<dyn RouteService>,
1735 context: eggress_server::ConnectionContext {
1736 source: Some(peer),
1737 listener: listener_str,
1738 generation,
1739 },
1740 handshake_timeout: hs_timeout,
1741 connect_timeout,
1742 protocols: conn_protocols,
1743 authentication: conn_auth,
1744 metrics: Some(conn_metrics),
1745 udp: udp_svc,
1746 tls_client_config,
1747 shadowsocks: ss_config.map(
1748 |ss| eggress_server::accept::InboundShadowsocksConfig {
1749 method: ss.method,
1750 password: ss.password,
1751 #[cfg(feature = "pproxy-legacy")]
1752 auth_prefix: ss.auth_prefix.map(String::into_bytes),
1753 #[cfg(feature = "pproxy-legacy")]
1754 plugins: ss.plugins,
1755 },
1756 ),
1757 #[cfg(feature = "extended")]
1758 shadowsocks_metrics: Some(conn_ss_metrics),
1759 #[cfg(not(feature = "extended"))]
1760 shadowsocks_metrics: None,
1761 trojan: trojan_config.map(
1762 |t| eggress_server::accept::InboundTrojanConfig {
1763 password: t.password,
1764 fallback: t.fallback,
1765 },
1766 ),
1767 fixed_target: None,
1768 local_bind: None,
1769 #[cfg(feature = "ssh")]
1770 ssh_sessions: Some(conn_ssh_sessions),
1771 };
1772
1773 let report = tokio::select! {
1774 report = eggress_server::serve_connection(stream, config)
1775 .instrument(tracing::info_span!(
1776 "conn",
1777 id = conn_id,
1778 peer = %peer,
1779 listener_type = "unix",
1780 listener = %listener_str_for_span,
1781 socket_path = %socket_path_clone,
1782 )) => {
1783 report
1784 }
1785 _ = conn_cancel.cancelled() => {
1786 eggress_server::SessionReport::cancelled(
1787 None,
1788 None,
1789 String::new(),
1790 )
1791 }
1792 };
1793
1794 active.fetch_sub(1, Ordering::Relaxed);
1795
1796 tracing::info!(
1797 protocol = ?report.protocol,
1798 target = ?report.target,
1799 route = %report.route,
1800 outcome = ?report.outcome,
1801 bytes_upstream = report.bytes_upstream,
1802 bytes_downstream = report.bytes_downstream,
1803 duration_ms = started.elapsed().as_millis() as u64,
1804 "unix connection completed",
1805 );
1806 });
1807 }
1808
1809 unix_listener.cleanup().unwrap_or_else(|e| {
1811 tracing::warn!("failed to cleanup unix socket: {e}");
1812 });
1813 });
1814 }
1815
1816 #[cfg(feature = "operations")]
1818 let compatibility_proxy_selection = prepared
1819 .iter()
1820 .find(|listener| {
1821 listener.protocols.contains(&ProtocolId::Socks5)
1822 && listener.local_addr.port() != 0
1823 })
1824 .map(|listener| {
1825 (
1826 eggress_system_proxy::CompatibilityProxyKind::Socks5,
1827 listener.local_addr.port(),
1828 )
1829 })
1830 .or_else(|| {
1831 prepared
1832 .iter()
1833 .find(|listener| {
1834 listener.protocols.contains(&ProtocolId::Http)
1835 && listener.local_addr.port() != 0
1836 })
1837 .map(|listener| {
1838 (
1839 eggress_system_proxy::CompatibilityProxyKind::Http,
1840 listener.local_addr.port(),
1841 )
1842 })
1843 });
1844
1845 if compatibility_options.system_proxy {
1850 #[cfg(feature = "operations")]
1851 {
1852 let selected = compatibility_proxy_selection.ok_or_else(|| {
1853 RuntimeError::Other(
1854 "--sys requires a usable local HTTP or SOCKS5 listener".to_string(),
1855 )
1856 })?;
1857 let address = std::net::SocketAddr::new(
1858 std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
1859 selected.1,
1860 );
1861 compatibility_system_proxy = Some(
1862 eggress_system_proxy::apply_compatibility_proxy(selected.0, address)
1863 .map_err(RuntimeError::Other)?,
1864 );
1865 }
1866 #[cfg(not(feature = "operations"))]
1867 {
1868 return Err(RuntimeError::Other(
1869 "--sys requires the operations feature".to_string(),
1870 ));
1871 }
1872 }
1873
1874 #[cfg(feature = "quic")]
1875 for prepared_listener in prepared_quic {
1876 let listener_name = prepared_listener.name.clone();
1877 let listener_protocols: Arc<[ProtocolId]> = prepared_listener
1878 .protocols
1879 .iter()
1880 .copied()
1881 .filter(|protocol| !matches!(protocol, ProtocolId::Quic | ProtocolId::Http3))
1882 .collect::<Vec<_>>()
1883 .into();
1884 let routing = routing.clone();
1885 let state = state_ref.clone();
1886 let conn_tasks = connection_tasks.clone();
1887 let conn_cancel = connection_cancel.clone();
1888 let listener_cancel = listener_cancel.clone();
1889 let is_h3 = prepared_listener.protocols.contains(&ProtocolId::Http3);
1890 let auth = prepared_listener.auth.clone();
1891 let handshake_timeout_for_listener = prepared_listener.handshake_timeout;
1892 let connection_limit = prepared_listener.connection_limit;
1893
1894 #[cfg(feature = "ssh")]
1895 let listener_ssh_sessions = ssh_sessions.clone();
1896
1897 if is_h3 {
1898 let listener = prepared_listener.listener.clone();
1899 let tls_client_config_for_listener = tls_client_config.clone();
1900 tasks.spawn(async move {
1901 let active_streams = Arc::new(AtomicU64::new(0));
1902 loop {
1903 let connection = match listener.accept_connection(&listener_cancel).await {
1904 Ok(Some(connection)) => connection,
1905 Ok(None) => break,
1906 Err(error) => {
1907 tracing::debug!(%error, listener = %listener_name, "H3 connection failed");
1908 continue;
1909 }
1910 };
1911 let routing = routing.clone();
1912 let state = state.clone();
1913 let conn_tasks = conn_tasks.clone();
1914 let conn_cancel = conn_cancel.child_token();
1915 let listener_name = listener_name.clone();
1916 let auth = auth.clone();
1917 let authorization = match &auth {
1918 eggress_server::accept::InboundAuthentication::None => None,
1919 eggress_server::accept::InboundAuthentication::UsernamePassword { username, password }
1920 | eggress_server::accept::InboundAuthentication::UsernamePasswordWithReuse { username, password, .. } => {
1921 Some((username.clone(), password.clone()))
1922 }
1923 };
1924 let active_streams = active_streams.clone();
1925 let protocols = listener_protocols.clone();
1926 let connection_limit = connection_limit;
1927 let tls_client_config_for_connection = tls_client_config_for_listener.clone();
1928 conn_tasks.spawn(async move {
1929 let result = eggress_protocol_h3::serve_connection(
1930 connection,
1931 conn_cancel.clone(),
1932 authorization,
1933 move |request, stream, peer| {
1934 let routing = routing.clone();
1935 let state = state.clone();
1936 let listener_name = listener_name.clone();
1937 let auth = auth.clone();
1938 let protocols = protocols.clone();
1939 let active_streams = active_streams.clone();
1940 let tls_client_config = tls_client_config_for_connection.clone();
1941 async move {
1942 let slot = match ListenerConnectionSlot::try_acquire(&active_streams, connection_limit) {
1943 Some(slot) => slot,
1944 None => return,
1945 };
1946 let target = match request.target() {
1947 Ok(target) => target,
1948 Err(error) => {
1949 tracing::debug!(%error, "invalid H3 CONNECT authority");
1950 drop(slot);
1951 return;
1952 }
1953 };
1954 let generation = state.snapshot.load().generation;
1955 let config = eggress_server::ConnectionConfig {
1956 routing: routing as Arc<dyn RouteService>,
1957 context: eggress_server::ConnectionContext {
1958 source: Some(peer),
1959 listener: listener_name,
1960 generation,
1961 },
1962 handshake_timeout: handshake_timeout_for_listener,
1963 connect_timeout,
1964 protocols,
1965 authentication: auth,
1966 metrics: Some(state.metrics.clone()),
1967 udp: None,
1968 tls_client_config,
1969 shadowsocks: None,
1970 #[cfg(feature = "extended")]
1971 shadowsocks_metrics: Some(state.shadowsocks_metrics.clone()),
1972 #[cfg(not(feature = "extended"))]
1973 shadowsocks_metrics: None,
1974 trojan: None,
1975 fixed_target: None,
1976 local_bind: None,
1977 #[cfg(feature = "ssh")]
1978 ssh_sessions: Some(listener_ssh_sessions.clone()),
1979 };
1980 let pending = eggress_server::accept::PendingTunnel {
1981 target: target.clone(),
1982 client: stream,
1983 protocol: eggress_server::accept::TunnelProtocol::Http3,
1984 reply_context: eggress_server::accept::ReplyContext::Http3,
1985 identity: eggress_core::ClientIdentity::Anonymous,
1986 };
1987 state.metrics.record_session_start();
1988 let report = eggress_server::execute::execute(
1989 eggress_server::accept::AcceptedSession::Tunnel(pending),
1990 &config,
1991 ).await;
1992 state.metrics.record_session(&report);
1993 drop(slot);
1994 }
1995 },
1996 ).await;
1997 if let Err(error) = result {
1998 tracing::debug!(%error, "H3 connection ended");
1999 }
2000 });
2001 }
2002 });
2003 } else {
2004 let listener = prepared_listener.listener.clone();
2005 let auth = prepared_listener.auth.clone();
2006 let tls_client_config_for_listener = tls_client_config.clone();
2007 tasks.spawn(async move {
2008 let active_streams = Arc::new(AtomicU64::new(0));
2009 let listener_name_for_handler = listener_name.clone();
2010 let protocols_for_handler = listener_protocols.clone();
2011 let routing_for_handler = routing.clone();
2012 let state_for_handler = state.clone();
2013 let auth_for_handler = auth.clone();
2014 #[cfg(feature = "ssh")]
2015 let listener_ssh_sessions_for_handler = Some(listener_ssh_sessions.clone());
2016 let result = listener
2017 .run(listener_cancel, move |stream, peer| {
2018 let routing = routing_for_handler.clone();
2019 let state = state_for_handler.clone();
2020 let listener_name = listener_name_for_handler.clone();
2021 let protocols = protocols_for_handler.clone();
2022 let auth = auth_for_handler.clone();
2023 let active_streams = active_streams.clone();
2024 #[cfg(feature = "ssh")]
2025 let ssh_sessions = listener_ssh_sessions_for_handler.clone();
2026 let tls_client_config = tls_client_config_for_listener.clone();
2027 async move {
2028 let Some(slot) = ListenerConnectionSlot::try_acquire(
2029 &active_streams,
2030 connection_limit,
2031 ) else {
2032 return;
2033 };
2034 let generation = state.snapshot.load().generation;
2035 let config = eggress_server::ConnectionConfig {
2036 routing: routing as Arc<dyn RouteService>,
2037 context: eggress_server::ConnectionContext {
2038 source: Some(peer),
2039 listener: listener_name,
2040 generation,
2041 },
2042 handshake_timeout: handshake_timeout_for_listener,
2043 connect_timeout,
2044 protocols,
2045 authentication: auth,
2046 metrics: Some(state.metrics.clone()),
2047 udp: None,
2048 tls_client_config,
2049 shadowsocks: None,
2050 #[cfg(feature = "extended")]
2051 shadowsocks_metrics: Some(
2052 state.shadowsocks_metrics.clone(),
2053 ),
2054 #[cfg(not(feature = "extended"))]
2055 shadowsocks_metrics: None,
2056 trojan: None,
2057 fixed_target: None,
2058 local_bind: None,
2059 #[cfg(feature = "ssh")]
2060 ssh_sessions,
2061 };
2062 let _ = eggress_server::serve_connection(stream, config).await;
2063 drop(slot);
2064 }
2065 })
2066 .await;
2067 if let Err(error) = result {
2068 tracing::debug!(%error, "QUIC listener ended");
2069 }
2070 });
2071 }
2072 }
2073
2074 for prepared_listener in prepared {
2075 let routing = routing.clone();
2076 let state = state_ref.clone();
2077 let conn_tasks = connection_tasks.clone();
2078 let conn_cancel = connection_cancel.clone();
2079 let tls_client_config = tls_client_config.clone();
2080
2081 #[cfg(feature = "ssh")]
2082 let listener_ssh_sessions = ssh_sessions.clone();
2083 tasks.spawn(async move {
2084 let proto_slice: Arc<[ProtocolId]> = prepared_listener.protocols.clone().into();
2085
2086 loop {
2087 let conn = match prepared_listener.listener.accept().await {
2088 Ok(c) => c,
2089 Err(e) => {
2090 if e.to_string().contains("listener cancelled") {
2091 break;
2092 }
2093 tracing::error!("accept error: {e}");
2094 continue;
2095 }
2096 };
2097
2098 let routing = routing.clone();
2099 let tls_client_config = tls_client_config.clone();
2100 let peer = conn.peer_addr;
2101 let listener_str = prepared_listener.name.clone();
2102 let conn_id = state.connection_counter.fetch_add(1, Ordering::Relaxed);
2103 let conn_protocols = proto_slice.clone();
2104 let conn_auth = prepared_listener.auth.clone();
2105 let conn_metrics = state.metrics.clone();
2106 #[cfg(feature = "extended")]
2107 let conn_ss_metrics = state.shadowsocks_metrics.clone();
2108 let active = state.active_connections.clone();
2109 let conn_cancel = conn_cancel.child_token();
2110 let generation = state.snapshot.load().generation;
2111
2112 active.fetch_add(1, Ordering::Relaxed);
2113
2114 let tls_config = prepared_listener.tls.clone();
2115 let ss_config = prepared_listener.shadowsocks.clone();
2116 let trojan_config = prepared_listener.trojan.clone();
2117 let fixed_target = prepared_listener.fixed_target.clone();
2118 let local_bind = prepared_listener.local_bind.clone();
2119
2120 let udp_svc = if let Some(ref udp_config) = prepared_listener.udp {
2121 Some(Arc::new(RuntimeUdpService {
2122 _listener_name: prepared_listener.name.clone(),
2123 udp_config: udp_config.clone(),
2124 registry: state.udp_registry.clone(),
2125 metrics: state.metrics.clone(),
2126 udp_metrics: state.udp_metrics.clone(),
2127 routing: routing.clone(),
2128 udp_tasks: state.udp_tasks.clone(),
2129 })
2130 as Arc<dyn eggress_server::UdpService>)
2131 } else {
2132 None
2133 };
2134 #[cfg(feature = "ssh")]
2135 let conn_ssh_sessions = listener_ssh_sessions.clone();
2136 conn_tasks.spawn(async move {
2137 let started = std::time::Instant::now();
2138
2139 let stream: eggress_core::BoxStream =
2141 if let Some(ref tls_cfg) = tls_config {
2142 let server_config =
2143 match eggress_transport_tls::TlsServerConfigBuilder::new()
2144 .with_certificate_pem(&tls_cfg.cert_pem)
2145 .and_then(|b| b.with_key_pem(&tls_cfg.key_pem))
2146 .and_then(|b| {
2147 let b = if tls_cfg.alpn.is_empty() {
2148 b
2149 } else {
2150 b.with_alpn(tls_cfg.alpn.clone())
2151 };
2152 b.build()
2153 }) {
2154 Ok(c) => c,
2155 Err(e) => {
2156 tracing::error!(%peer, "TLS config error: {e}");
2157 active.fetch_sub(1, Ordering::Relaxed);
2158 return;
2159 }
2160 };
2161 match eggress_transport_tls::tls_accept(
2162 Box::new(conn.stream),
2163 server_config,
2164 )
2165 .await
2166 {
2167 Ok(s) => s,
2168 Err(e) => {
2169 tracing::debug!(%peer, "TLS accept failed: {e}");
2170 active.fetch_sub(1, Ordering::Relaxed);
2171 return;
2172 }
2173 }
2174 } else {
2175 Box::new(conn.stream)
2176 };
2177
2178 #[cfg(feature = "extended")]
2179 let advanced_protocol = conn_protocols.first().copied();
2180 #[cfg(feature = "extended")]
2181 let advanced_is_single = conn_protocols.len() == 1;
2182 #[cfg(feature = "extended")]
2183 let advanced_fixed_target = fixed_target.clone();
2184 let config = eggress_server::ConnectionConfig {
2185 routing: routing as Arc<dyn RouteService>,
2186 context: eggress_server::ConnectionContext {
2187 source: Some(peer),
2188 listener: listener_str,
2189 generation,
2190 },
2191 handshake_timeout: prepared_listener.handshake_timeout,
2192 connect_timeout,
2193 protocols: conn_protocols,
2194 authentication: conn_auth,
2195 metrics: Some(conn_metrics),
2196 udp: udp_svc,
2197 tls_client_config: tls_client_config.clone(),
2198 shadowsocks: ss_config.map(|ss| {
2199 eggress_server::accept::InboundShadowsocksConfig {
2200 method: ss.method,
2201 password: ss.password,
2202 #[cfg(feature = "pproxy-legacy")]
2203 auth_prefix: ss.auth_prefix.map(String::into_bytes),
2204 #[cfg(feature = "pproxy-legacy")]
2205 plugins: ss.plugins,
2206 }
2207 }),
2208 #[cfg(feature = "extended")]
2209 shadowsocks_metrics: Some(conn_ss_metrics),
2210 #[cfg(not(feature = "extended"))]
2211 shadowsocks_metrics: None,
2212 trojan: trojan_config.map(|t| {
2213 eggress_server::accept::InboundTrojanConfig {
2214 password: t.password,
2215 fallback: t.fallback,
2216 }
2217 }),
2218 fixed_target,
2219 local_bind,
2220 #[cfg(feature = "ssh")]
2221 ssh_sessions: Some(conn_ssh_sessions),
2222 };
2223
2224 #[cfg(feature = "extended")]
2225 if matches!(
2226 advanced_protocol,
2227 Some(ProtocolId::Http2 | ProtocolId::WebSocket)
2228 ) && advanced_is_single
2229 {
2230 let advanced_result = match advanced_protocol {
2231 Some(ProtocolId::Http2) => {
2232 eggress_server::advanced::serve_h2_connection(
2233 stream, config,
2234 )
2235 .await
2236 }
2237 Some(ProtocolId::WebSocket) => match advanced_fixed_target {
2238 Some(target) => {
2239 eggress_server::advanced::serve_websocket_connection(
2240 stream, config, target,
2241 )
2242 .await
2243 }
2244 None => Err("WebSocket listener requires a fixed target"
2245 .to_string()),
2246 },
2247 _ => unreachable!(),
2248 };
2249 if let Err(error) = advanced_result {
2250 tracing::debug!(%peer, %error, "advanced listener ended");
2251 }
2252 active.fetch_sub(1, Ordering::Relaxed);
2253 return;
2254 }
2255
2256 let report = tokio::select! {
2257 report = eggress_server::serve_connection(stream, config)
2258 .instrument(tracing::info_span!(
2259 "conn",
2260 id = conn_id,
2261 peer = %peer,
2262 )) => {
2263 report
2264 }
2265 _ = conn_cancel.cancelled() => {
2266 eggress_server::SessionReport::cancelled(
2267 None,
2268 None,
2269 String::new(),
2270 )
2271 }
2272 };
2273
2274 active.fetch_sub(1, Ordering::Relaxed);
2275
2276 if compatibility_options.debug && report.failure.is_some() {
2277 tracing::error!(
2278 protocol = ?report.protocol,
2279 target = ?report.target,
2280 route = %report.route,
2281 outcome = ?report.outcome,
2282 failure = ?report.failure,
2283 "pproxy debug connection failure",
2284 );
2285 } else {
2286 tracing::info!(
2287 protocol = ?report.protocol,
2288 target = ?report.target,
2289 route = %report.route,
2290 outcome = ?report.outcome,
2291 bytes_upstream = report.bytes_upstream,
2292 bytes_downstream = report.bytes_downstream,
2293 duration_ms = started.elapsed().as_millis() as u64,
2294 "connection completed",
2295 );
2296 }
2297 if compatibility_options.verbose_level >= 1 {
2298 tracing::info!(
2299 protocol = ?report.protocol,
2300 target = ?report.target,
2301 route = %report.route,
2302 "pproxy connection event",
2303 );
2304 }
2305 if compatibility_options.verbose_level >= 2 {
2306 tracing::info!(
2307 bytes_upstream = report.bytes_upstream,
2308 bytes_downstream = report.bytes_downstream,
2309 "pproxy traffic stats",
2310 );
2311 }
2312 });
2313 }
2314 });
2315 }
2316
2317 #[cfg(feature = "reverse")]
2319 {
2320 let current_snapshot = snapshot.load();
2321 let reverse_servers = current_snapshot.reverse_servers.clone();
2322 let reverse_clients = current_snapshot.reverse_clients.clone();
2323 drop(current_snapshot);
2324
2325 for rs_cfg in reverse_servers {
2326 if rs_cfg.pproxy_compat {
2327 let server_config =
2328 eggress_protocol_reverse::compat_pproxy::PproxyBackwardServerConfig {
2329 control_bind: rs_cfg.control_bind,
2330 external_bind: rs_cfg.external_bind,
2331 auth: eggress_protocol_reverse::compat_pproxy::raw_auth(
2332 rs_cfg.auth_username.as_deref(),
2333 rs_cfg.auth_password.as_deref(),
2334 ),
2335 max_control_connections: rs_cfg.max_control_connections as usize,
2336 max_pending_external: rs_cfg.max_pending_external as usize,
2337 read_timeout_ms: rs_cfg.read_timeout_ms,
2338 socks5_target: None,
2339 client_framing:
2340 eggress_protocol_reverse::compat_pproxy::PproxyBackwardFraming::Raw,
2341 };
2342 let server =
2343 eggress_protocol_reverse::compat_pproxy::PproxyBackwardServer::new(
2344 server_config,
2345 );
2346 let server_cancel = server.cancel_token();
2347 let cancel_clone = cancel.clone();
2348 tasks.spawn(async move {
2349 let result = tokio::select! {
2350 r = server.run() => r,
2351 _ = cancel_clone.cancelled() => {
2352 server_cancel.cancel();
2353 Ok(())
2354 }
2355 };
2356 if let Err(e) = result {
2357 tracing::error!(error = %e, "pproxy backward server error");
2358 }
2359 });
2360 continue;
2361 }
2362 let server_config = eggress_protocol_reverse::server::ReverseServerConfig {
2363 control_bind: rs_cfg.control_bind,
2364 external_bind: Some(rs_cfg.external_bind),
2365 auth_username: rs_cfg.auth_username,
2366 auth_password: rs_cfg.auth_password,
2367 max_control_connections: rs_cfg.max_control_connections,
2368 read_timeout_ms: rs_cfg.read_timeout_ms,
2369 allow_bind: rs_cfg.allow_bind,
2370 max_listeners_per_client: rs_cfg.max_listeners_per_client,
2371 max_streams_per_listener: rs_cfg.max_streams_per_listener,
2372 max_pending_external: rs_cfg.max_pending_external,
2373 };
2374 if let Err(e) = server_config.validate() {
2378 tracing::error!(
2379 server_id = %rs_cfg.id,
2380 error = %e,
2381 "reverse server configuration validation failed; skipping",
2382 );
2383 continue;
2384 }
2385 let mut server =
2386 eggress_protocol_reverse::server::ReverseServer::new(server_config);
2387 server.set_metrics(state_ref.reverse_metrics.clone());
2388 let server_state = server.state_handle();
2389 let server_cancel = server.cancel_token();
2390
2391 state_ref
2392 .reverse_registry
2393 .register(eggress_admin::ReverseServerEntry {
2394 id: eggress_admin::ReverseServerId::from(rs_cfg.id.as_str()),
2395 control_bind: rs_cfg.control_bind.to_string(),
2396 state: server_state,
2397 });
2398
2399 let cancel_clone = cancel.clone();
2400 tasks.spawn(async move {
2401 let result = tokio::select! {
2402 r = server.run() => r,
2403 _ = cancel_clone.cancelled() => {
2404 server_cancel.cancel();
2405 Ok(())
2406 }
2407 };
2408 if let Err(e) = result {
2409 tracing::error!(error = %e, "reverse server error");
2410 }
2411 });
2412 }
2413
2414 for rc_cfg in reverse_clients {
2415 let host = rc_cfg
2416 .default_target_host
2417 .clone()
2418 .unwrap_or_else(|| "127.0.0.1".to_string());
2419 let port = rc_cfg.default_target_port.unwrap_or(0);
2420
2421 let parallel = rc_cfg.parallel_connections.max(1);
2422 for conn_idx in 0..parallel {
2423 if rc_cfg.pproxy_compat {
2424 let client_config = eggress_protocol_reverse::compat_pproxy::PproxyBackwardClientConfig {
2425 server_addr: rc_cfg.server_addr,
2426 server_chain: rc_cfg.server_chain.clone(),
2427 auth: eggress_protocol_reverse::compat_pproxy::raw_auth(
2428 rc_cfg.auth_username.as_deref(),
2429 rc_cfg.auth_password.as_deref(),
2430 ),
2431 reconnect_initial_ms: rc_cfg.reconnect_initial_ms,
2432 reconnect_max_ms: rc_cfg.reconnect_max_ms,
2433 read_timeout_ms: rc_cfg.read_timeout_ms,
2434 target_connect_timeout_ms: 10_000,
2435 server_framing:
2436 eggress_protocol_reverse::compat_pproxy::PproxyBackwardFraming::Raw,
2437 };
2438 let client =
2439 eggress_protocol_reverse::compat_pproxy::PproxyBackwardClient::new(
2440 client_config,
2441 std::sync::Arc::new(
2442 crate::reverse::RouteEngineTargetResolver::new(
2443 routing.clone(),
2444 host.clone(),
2445 port,
2446 std::sync::Arc::from(rc_cfg.id.as_str()),
2447 Some(rc_cfg.server_addr),
2448 ),
2449 ),
2450 );
2451 let cancel_clone = cancel.clone();
2452 let client_cancel = client.cancel_token();
2453 let client_id = rc_cfg.id.clone();
2454 let server_addr = rc_cfg.server_addr;
2455 tasks.spawn(async move {
2456 let result = tokio::select! {
2457 r = client.run() => r,
2458 _ = cancel_clone.cancelled() => {
2459 client_cancel.cancel();
2460 Ok(())
2461 }
2462 };
2463 if let Err(e) = result {
2464 tracing::error!(error = %e, client_id = %client_id, server = %server_addr, conn = conn_idx, "pproxy backward client error");
2465 }
2466 });
2467 continue;
2468 }
2469 let client_config = eggress_protocol_reverse::client::ReverseClientConfig {
2470 server_addr: rc_cfg.server_addr,
2471 auth_username: rc_cfg.auth_username.clone(),
2472 auth_password: rc_cfg.auth_password.clone(),
2473 reconnect_initial_ms: rc_cfg.reconnect_initial_ms,
2474 reconnect_max_ms: rc_cfg.reconnect_max_ms,
2475 default_target_host: rc_cfg.default_target_host.clone(),
2476 default_target_port: rc_cfg.default_target_port,
2477 read_timeout_ms: rc_cfg.read_timeout_ms,
2478 drain_grace_ms: rc_cfg.drain_grace_ms,
2479 ..Default::default()
2480 };
2481 let mut client =
2482 eggress_protocol_reverse::client::ReverseClient::new(client_config);
2483 client.set_metrics(state_ref.reverse_metrics.clone());
2484
2485 let resolver = crate::reverse::RouteEngineTargetResolver::new(
2486 routing.clone(),
2487 host.clone(),
2488 port,
2489 std::sync::Arc::from(rc_cfg.id.as_str()),
2490 Some(rc_cfg.server_addr),
2491 );
2492 client.set_resolver(std::sync::Arc::new(resolver));
2493
2494 let cancel_clone = cancel.clone();
2495 let client_cancel = client.cancel_token();
2496 let client_id = rc_cfg.id.clone();
2497 let server_addr = rc_cfg.server_addr;
2498
2499 tasks.spawn(async move {
2500 let result = tokio::select! {
2501 r = client.run() => r,
2502 _ = cancel_clone.cancelled() => {
2503 client_cancel.cancel();
2504 Ok(())
2505 }
2506 };
2507 if let Err(e) = result {
2508 tracing::error!(error = %e, client_id = %client_id, server = %server_addr, conn = conn_idx, "reverse client error");
2509 }
2510 });
2511 }
2512 }
2513 }
2514
2515 #[cfg(feature = "operations")]
2518 let pre_bound_admin = if let Some(ref admin_cfg) = admin_config {
2519 if admin_cfg.enabled {
2520 let bind = admin_cfg.bind.clone();
2521 let admin_cancel_token = admin_cancel.clone();
2522 let server =
2523 match eggress_admin::AdminServer::new(&bind, admin_cancel_token).await {
2524 Ok(s) => Some(s),
2525 Err(e) => {
2526 return Err(RuntimeError::ListenerBind {
2527 addr: bind,
2528 source: std::io::Error::new(
2529 std::io::ErrorKind::AddrInUse,
2530 e.to_string(),
2531 ),
2532 });
2533 }
2534 };
2535 server
2536 } else {
2537 None
2538 }
2539 } else {
2540 None
2541 };
2542
2543 #[cfg(feature = "operations")]
2544 if let (Some(server), Some(admin_cfg)) = (pre_bound_admin, admin_config.as_ref()) {
2545 let metrics_enabled = admin_cfg.metrics;
2546 let state_ref = state_ref.clone();
2547 let provider: Arc<dyn AdminSnapshotProvider> = listener_infos_provider.clone();
2548 if let Ok(addr) = server.local_addr() {
2549 *state_ref
2550 .admin_local_addr
2551 .lock()
2552 .unwrap_or_else(|e| e.into_inner()) = Some(addr);
2553 }
2554 let admin_auth = admin_cfg.auth.clone();
2555 admin_tasks.spawn(async move {
2556 let admin_state = eggress_admin::AdminState {
2557 metrics: metrics_registry.clone(),
2558 start_time: state_ref.start_time,
2559 readiness: state_ref.readiness.clone(),
2560 active_connections: Some(state_ref.active_connections.clone()),
2561 provider,
2562 udp_registry: state_ref.udp_registry.clone(),
2563 #[cfg(feature = "reverse")]
2564 reverse_registry: state_ref.reverse_registry.clone(),
2565 #[cfg(not(feature = "reverse"))]
2566 reverse_registry: std::sync::Arc::new(eggress_admin::ReverseRegistry::new()),
2567 metrics_enabled,
2568 auth: admin_auth,
2569 };
2570 if let Err(e) = server.run(admin_state).await {
2571 tracing::error!("admin server error: {e}");
2572 }
2573 });
2574 }
2575
2576 readiness.store(true, Ordering::Release);
2577
2578 #[cfg(unix)]
2579 {
2580 let mut sigterm =
2581 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate());
2582 let mut sighup =
2583 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup());
2584
2585 if let Err(ref e) = sigterm {
2586 tracing::warn!("failed to register SIGTERM handler: {e}");
2587 }
2588 if let Err(ref e) = sighup {
2589 tracing::warn!("failed to register SIGHUP handler: {e}");
2590 }
2591
2592 loop {
2593 tokio::select! {
2594 _ = cancel.cancelled() => {
2595 tracing::info!("shutdown requested via cancel token");
2596 break;
2597 }
2598 _ = tokio::signal::ctrl_c() => {
2599 tracing::info!("shutdown signal received");
2600 break;
2601 }
2602 _ = async { sigterm.as_mut().ok()?.recv().await }, if sigterm.is_ok() => {
2603 tracing::info!("shutdown signal received");
2604 break;
2605 }
2606 _ = async { sighup.as_mut().ok()?.recv().await }, if sighup.is_ok() && !config_path.is_empty() => {
2607 tracing::info!("reload signal received, reloading config from {config_path}");
2608 let prev_snapshot = snapshot.load();
2609 let prev_ref: Option<&CompiledRuntimeSnapshot> = Some(&prev_snapshot);
2610 let config_path_clone = config_path.clone();
2611 let load_result = tokio::task::spawn_blocking(move || {
2612 eggress_config::compile::load_and_compile(&config_path_clone)
2613 }).await;
2614 match load_result {
2615 Ok(Ok(new_rt_config)) => {
2616 let old_listeners = &snapshot.load().listeners;
2618 if let Err(reason) = classify_listeners(old_listeners, &new_rt_config.listeners) {
2619 tracing::error!("reload rejected: {reason}");
2620 metrics.record_reload(false);
2621 continue;
2622 }
2623 match compile_runtime_snapshot(&new_rt_config, prev_ref) {
2624 Ok(new_snapshot) => {
2625 let upstream_count = new_snapshot.upstreams.len();
2626 let gen = new_snapshot.generation;
2627
2628 let new_snapshot = Arc::new(new_snapshot);
2633 snapshot.store(new_snapshot.clone());
2634 routing.swap_arc(new_snapshot.router.clone());
2635
2636 metrics.set_config_generation(gen);
2637 metrics.record_reload(true);
2638
2639 if let Ok(mut guard) = health_clone.lock() {
2640 if let Some(ref mut hm) = *guard {
2641 hm.stop_all();
2642 }
2643 let upstream_runtimes: Vec<Arc<UpstreamRuntime>> = snapshot
2644 .load()
2645 .upstreams
2646 .values()
2647 .cloned()
2648 .collect();
2649 if !upstream_runtimes.is_empty() {
2650 let mut hm = HealthManager::new(health_cancel.clone());
2651 hm.start_probes(&upstream_runtimes);
2652 *guard = Some(hm);
2653 } else {
2654 *guard = None;
2655 }
2656 }
2657
2658 tracing::info!(
2659 generation = gen,
2660 upstreams = upstream_count,
2661 "config reloaded successfully"
2662 );
2663 }
2664 Err(e) => {
2665 metrics.record_reload(false);
2666 tracing::error!("reload failed (snapshot build): {e}");
2667 }
2668 }
2669 }
2670 Ok(Err(e)) => {
2671 metrics.record_reload(false);
2672 tracing::error!("reload failed (config load): {e}");
2673 }
2674 Err(join_err) => {
2675 metrics.record_reload(false);
2676 tracing::error!("reload task panicked: {join_err}");
2677 }
2678 }
2679 }
2680 }
2681 }
2682 }
2683
2684 #[cfg(not(unix))]
2685 {
2686 tokio::select! {
2687 _ = cancel.cancelled() => {
2688 tracing::info!("shutdown requested via cancel token");
2689 }
2690 _ = tokio::signal::ctrl_c() => {
2691 tracing::info!("shutdown signal received");
2692 }
2693 }
2694 }
2695
2696 readiness.store(false, Ordering::Release);
2698
2699 listener_cancel.cancel();
2701
2702 health_cancel.cancel();
2704
2705 state_ref.udp_registry.close_all().await;
2707
2708 state_ref.udp_tasks.close();
2710 let _ = tokio::time::timeout(shutdown_grace, state_ref.udp_tasks.wait()).await;
2711
2712 tasks.close();
2715 tasks.wait().await;
2716
2717 tracing::info!("draining active connections");
2721
2722 let deadline = tokio::time::Instant::now() + shutdown_grace;
2723 loop {
2724 let active = active_connections.load(Ordering::Relaxed);
2725 if active == 0 {
2726 tracing::info!("all connections drained");
2727 break;
2728 }
2729 if tokio::time::Instant::now() >= deadline {
2730 tracing::warn!(active, "drain timeout reached, forcing shutdown");
2731 connection_cancel.cancel();
2732 break;
2733 }
2734 tokio::time::sleep(Duration::from_millis(100)).await;
2735 }
2736
2737 connection_tasks.close();
2739 connection_tasks.wait().await;
2740
2741 #[cfg(feature = "ssh")]
2742 ssh_sessions.shutdown().await;
2743
2744 admin_cancel.cancel();
2748 admin_tasks.close();
2749 admin_tasks.wait().await;
2750
2751 #[cfg(feature = "operations")]
2752 if let Some(mut proxy) = compatibility_system_proxy {
2753 proxy.restore().map_err(RuntimeError::Other)?;
2754 }
2755
2756 Ok::<_, RuntimeError>(())
2757 };
2758
2759 let result = if tokio::runtime::Handle::try_current().is_err() {
2760 let rt = tokio::runtime::Runtime::new()?;
2762 rt.block_on(run_async)
2763 } else {
2764 std::thread::Builder::new()
2769 .name("eggress-supervisor".to_string())
2770 .spawn(move || -> Result<(), RuntimeError> {
2771 let rt = tokio::runtime::Builder::new_multi_thread()
2772 .enable_all()
2773 .build()
2774 .map_err(RuntimeError::RuntimeInit)?;
2775 rt.block_on(run_async)
2776 })
2777 .map_err(RuntimeError::RuntimeInit)?
2778 .join()
2779 .map_err(|payload| {
2780 let message = payload
2781 .downcast_ref::<String>()
2782 .map(String::as_str)
2783 .or_else(|| payload.downcast_ref::<&'static str>().copied())
2784 .unwrap_or("unknown panic payload");
2785 RuntimeError::Other(format!("supervisor thread panicked: {message}"))
2786 })?
2787 };
2788
2789 self.health = std::sync::Arc::try_unwrap(health)
2790 .ok()
2791 .and_then(|m| m.into_inner().ok())
2792 .flatten();
2793
2794 match &result {
2795 Ok(()) => tracing::info!("eggress stopped"),
2796 Err(e) => tracing::error!(error = %e, "eggress stopped with error"),
2797 }
2798
2799 result
2800 }
2801}
2802
2803#[cfg(test)]
2804mod tests {
2805 use super::*;
2806 use std::io::Write;
2807 use tempfile::NamedTempFile;
2808
2809 use crate::snapshot::compile_runtime_snapshot;
2810 use eggress_config::compile::{GroupFallback, ProcessConfig, RuntimeConfig, TimeoutConfig};
2811 use eggress_routing::scheduler::SchedulerKind;
2812 use eggress_routing::{MatchExpr, RouteActionSpec, RuleId, UpstreamGroupId};
2813
2814 fn write_config(content: &str) -> NamedTempFile {
2815 let mut f = NamedTempFile::new().unwrap();
2816 f.write_all(content.as_bytes()).unwrap();
2817 f.flush().unwrap();
2818 f
2819 }
2820
2821 #[test]
2822 fn build_router_direct_only() {
2823 let rt_config = RuntimeConfig {
2824 process: ProcessConfig::default(),
2825 timeouts: TimeoutConfig::default(),
2826 listeners: vec![],
2827 upstreams: vec![],
2828 groups: vec![],
2829 rules: vec![],
2830 default_action: RouteActionSpec::Direct,
2831 admin: None,
2832 reverse_servers: vec![],
2833 reverse_clients: vec![],
2834 };
2835 let snap = compile_runtime_snapshot(&rt_config, None).unwrap();
2836 assert!(snap.router.rules().is_empty());
2837 }
2838
2839 #[test]
2840 fn build_router_with_group_references_unknown_upstream() {
2841 let rt_config = RuntimeConfig {
2842 process: ProcessConfig::default(),
2843 timeouts: TimeoutConfig::default(),
2844 listeners: vec![],
2845 upstreams: vec![],
2846 groups: vec![eggress_config::compile::UpstreamGroupConfig {
2847 id: UpstreamGroupId(Arc::from("main")),
2848 scheduler: SchedulerKind::RoundRobin,
2849 members: vec!["nonexistent".to_string()],
2850 fallback: GroupFallback::Reject,
2851 }],
2852 rules: vec![],
2853 default_action: RouteActionSpec::Direct,
2854 admin: None,
2855 reverse_servers: vec![],
2856 reverse_clients: vec![],
2857 };
2858 let result = compile_runtime_snapshot(&rt_config, None);
2859 assert!(result.is_err(), "expected error, got Ok");
2860 let err_msg = result.err().unwrap().to_string();
2861 assert!(err_msg.contains("nonexistent"));
2862 }
2863
2864 #[test]
2865 fn build_router_with_valid_group() {
2866 let rt_config = RuntimeConfig {
2867 process: ProcessConfig::default(),
2868 timeouts: TimeoutConfig::default(),
2869 listeners: vec![],
2870 upstreams: vec![eggress_config::compile::UpstreamConfig {
2871 id: "proxy1".to_string(),
2872 chain: eggress_uri::ProxyChainSpec { hops: vec![] },
2873 health: eggress_routing::health::HealthConfig::default(),
2874 h2: None,
2875 }],
2876 groups: vec![eggress_config::compile::UpstreamGroupConfig {
2877 id: UpstreamGroupId(Arc::from("main")),
2878 scheduler: SchedulerKind::RoundRobin,
2879 members: vec!["proxy1".to_string()],
2880 fallback: GroupFallback::Reject,
2881 }],
2882 rules: vec![],
2883 default_action: RouteActionSpec::Direct,
2884 admin: None,
2885 reverse_servers: vec![],
2886 reverse_clients: vec![],
2887 };
2888 let snap = compile_runtime_snapshot(&rt_config, None).unwrap();
2889 assert!(snap.router.rules().is_empty());
2890 }
2891
2892 #[test]
2893 fn build_router_rule_references_unknown_group() {
2894 let rt_config = RuntimeConfig {
2895 process: ProcessConfig::default(),
2896 timeouts: TimeoutConfig::default(),
2897 listeners: vec![],
2898 upstreams: vec![],
2899 groups: vec![],
2900 rules: vec![eggress_routing::CompiledRule {
2901 id: RuleId(Arc::from("r1")),
2902 matcher: MatchExpr::Any,
2903 action: RouteActionSpec::UpstreamGroup(UpstreamGroupId(Arc::from("missing"))),
2904 }],
2905 default_action: RouteActionSpec::Direct,
2906 admin: None,
2907 reverse_servers: vec![],
2908 reverse_clients: vec![],
2909 };
2910 let result = compile_runtime_snapshot(&rt_config, None);
2911 assert!(result.is_err(), "expected error, got Ok");
2912 let err_msg = result.err().unwrap().to_string();
2913 assert!(err_msg.contains("missing"));
2914 }
2915
2916 #[tokio::test]
2917 async fn load_config_start_supervisor() {
2918 let config = r#"
2919version = 1
2920
2921[[listeners]]
2922name = "test"
2923bind = "127.0.0.1:0"
2924protocols = ["http"]
2925"#;
2926 let f = write_config(config);
2927 let path = f.path().to_str().unwrap();
2928 let result = ServiceSupervisor::start(path);
2929 assert!(result.is_ok(), "expected Ok, got {:?}", result.err());
2930 }
2931
2932 #[test]
2933 fn active_connections_counter_increments_and_decrements() {
2934 let active = Arc::new(AtomicU64::new(0));
2935 assert_eq!(active.load(Ordering::Relaxed), 0);
2936 active.fetch_add(1, Ordering::Relaxed);
2937 assert_eq!(active.load(Ordering::Relaxed), 1);
2938 active.fetch_add(1, Ordering::Relaxed);
2939 assert_eq!(active.load(Ordering::Relaxed), 2);
2940 active.fetch_sub(1, Ordering::Relaxed);
2941 assert_eq!(active.load(Ordering::Relaxed), 1);
2942 active.fetch_sub(1, Ordering::Relaxed);
2943 assert_eq!(active.load(Ordering::Relaxed), 0);
2944 }
2945
2946 #[test]
2947 fn readiness_flag_controls_ready_endpoint() {
2948 let readiness = Arc::new(AtomicBool::new(true));
2949 assert!(readiness.load(Ordering::Relaxed));
2950 readiness.store(false, Ordering::Relaxed);
2951 assert!(!readiness.load(Ordering::Relaxed));
2952 readiness.store(true, Ordering::Relaxed);
2953 assert!(readiness.load(Ordering::Relaxed));
2954 }
2955
2956 #[test]
2957 fn reload_rejects_listener_name_change() {
2958 let config1 = r#"
2959version = 1
2960
2961[[listeners]]
2962name = "http-in"
2963bind = "127.0.0.1:8080"
2964protocols = ["http"]
2965"#;
2966 let config2 = r#"
2967version = 1
2968
2969[[listeners]]
2970name = "http-changed"
2971bind = "127.0.0.1:8080"
2972protocols = ["http"]
2973"#;
2974 let f1 = write_config(config1);
2975 let f2 = write_config(config2);
2976 let path1 = f1.path().to_str().unwrap();
2977 let path2 = f2.path().to_str().unwrap();
2978
2979 let sup = ServiceSupervisor::start(path1).unwrap();
2980 let new_config = eggress_config::compile::load_and_compile(path2).unwrap();
2981 let result = sup.classify_reload(&new_config);
2982 assert!(result.is_err(), "listener name change should be rejected");
2983 assert!(result.unwrap_err().contains("name changed"));
2984 }
2985
2986 #[test]
2987 fn reload_rejects_listener_bind_change() {
2988 let config1 = r#"
2989version = 1
2990
2991[[listeners]]
2992name = "http-in"
2993bind = "127.0.0.1:8080"
2994protocols = ["http"]
2995"#;
2996 let config2 = r#"
2997version = 1
2998
2999[[listeners]]
3000name = "http-in"
3001bind = "127.0.0.1:9090"
3002protocols = ["http"]
3003"#;
3004 let f1 = write_config(config1);
3005 let f2 = write_config(config2);
3006 let path1 = f1.path().to_str().unwrap();
3007 let path2 = f2.path().to_str().unwrap();
3008
3009 let sup = ServiceSupervisor::start(path1).unwrap();
3010 let new_config = eggress_config::compile::load_and_compile(path2).unwrap();
3011 let result = sup.classify_reload(&new_config);
3012 assert!(result.is_err(), "listener bind change should be rejected");
3013 assert!(result.unwrap_err().contains("bind"));
3014 }
3015
3016 #[test]
3017 fn reload_accepts_unchanged_listeners() {
3018 let config = r#"
3019version = 1
3020
3021[[listeners]]
3022name = "http-in"
3023bind = "127.0.0.1:8080"
3024protocols = ["http"]
3025"#;
3026 let f = write_config(config);
3027 let path = f.path().to_str().unwrap();
3028
3029 let sup = ServiceSupervisor::start(path).unwrap();
3030 let new_config = eggress_config::compile::load_and_compile(path).unwrap();
3031 let result = sup.classify_reload(&new_config);
3032 assert!(result.is_ok(), "unchanged listeners should be accepted");
3033 }
3034
3035 #[test]
3036 fn reload_rejects_listener_count_change() {
3037 let config1 = r#"
3038version = 1
3039
3040[[listeners]]
3041name = "http-in"
3042bind = "127.0.0.1:8080"
3043protocols = ["http"]
3044"#;
3045 let config2 = r#"
3046version = 1
3047
3048[[listeners]]
3049name = "http-in"
3050bind = "127.0.0.1:8080"
3051protocols = ["http"]
3052
3053[[listeners]]
3054name = "socks-in"
3055bind = "127.0.0.1:1080"
3056protocols = ["socks5"]
3057"#;
3058 let f1 = write_config(config1);
3059 let f2 = write_config(config2);
3060 let path1 = f1.path().to_str().unwrap();
3061 let path2 = f2.path().to_str().unwrap();
3062
3063 let sup = ServiceSupervisor::start(path1).unwrap();
3064 let new_config = eggress_config::compile::load_and_compile(path2).unwrap();
3065 let result = sup.classify_reload(&new_config);
3066 assert!(result.is_err(), "listener count change should be rejected");
3067 assert!(result.unwrap_err().contains("listener count"));
3068 }
3069
3070 #[test]
3071 fn reload_rejects_transparent_enabled_change() {
3072 let config1 = r#"
3073version = 1
3074
3075[[listeners]]
3076name = "http-in"
3077bind = "127.0.0.1:8080"
3078protocols = ["http"]
3079"#;
3080 let config2 = r#"
3081version = 1
3082
3083[[listeners]]
3084name = "http-in"
3085bind = "127.0.0.1:8080"
3086protocols = ["http"]
3087
3088[listeners.transparent]
3089enabled = true
3090"#;
3091 let f1 = write_config(config1);
3092 let f2 = write_config(config2);
3093 let path1 = f1.path().to_str().unwrap();
3094 let path2 = f2.path().to_str().unwrap();
3095
3096 let sup = ServiceSupervisor::start(path1).unwrap();
3097 let new_config = eggress_config::compile::load_and_compile(path2).unwrap();
3098 let result = sup.classify_reload(&new_config);
3099 assert!(
3100 result.is_err(),
3101 "transparent enabled change should be rejected"
3102 );
3103 assert!(result.unwrap_err().contains("transparent"));
3104 }
3105
3106 #[test]
3107 fn reload_rejects_unix_path_change() {
3108 let config1 = r#"
3109version = 1
3110
3111[[listeners]]
3112name = "http-in"
3113bind = "127.0.0.1:8080"
3114protocols = ["http"]
3115
3116[listeners.unix]
3117path = "/tmp/eggress.sock"
3118"#;
3119 let config2 = r#"
3120version = 1
3121
3122[[listeners]]
3123name = "http-in"
3124bind = "127.0.0.1:8080"
3125protocols = ["http"]
3126
3127[listeners.unix]
3128path = "/tmp/eggress-new.sock"
3129"#;
3130 let f1 = write_config(config1);
3131 let f2 = write_config(config2);
3132 let path1 = f1.path().to_str().unwrap();
3133 let path2 = f2.path().to_str().unwrap();
3134
3135 let sup = ServiceSupervisor::start(path1).unwrap();
3136 let new_config = eggress_config::compile::load_and_compile(path2).unwrap();
3137 let result = sup.classify_reload(&new_config);
3138 assert!(result.is_err(), "unix path change should be rejected");
3139 assert!(result.unwrap_err().contains("unix socket path"));
3140 }
3141
3142 #[test]
3143 fn compute_advertise_explicit() {
3144 let result = compute_advertise_ip(
3145 Some("10.0.0.1".parse().unwrap()),
3146 "0.0.0.0".parse().unwrap(),
3147 "127.0.0.1:5000".parse().unwrap(),
3148 );
3149 assert_eq!(
3150 result.unwrap(),
3151 std::net::IpAddr::V4("10.0.0.1".parse().unwrap())
3152 );
3153 }
3154
3155 #[test]
3156 fn compute_advertise_bind_ip() {
3157 let result = compute_advertise_ip(
3158 None,
3159 "192.168.1.1".parse().unwrap(),
3160 "127.0.0.1:5000".parse().unwrap(),
3161 );
3162 assert_eq!(
3163 result.unwrap(),
3164 std::net::IpAddr::V4("192.168.1.1".parse().unwrap())
3165 );
3166 }
3167
3168 #[test]
3169 fn compute_advertise_loopback_fallback() {
3170 let result = compute_advertise_ip(
3171 None,
3172 "0.0.0.0".parse().unwrap(),
3173 "127.0.0.1:5000".parse().unwrap(),
3174 );
3175 assert_eq!(
3176 result.unwrap(),
3177 std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)
3178 );
3179 }
3180
3181 #[test]
3182 fn compute_advertise_unspecified_non_loopback_rejected() {
3183 let result = compute_advertise_ip(
3184 None,
3185 "0.0.0.0".parse().unwrap(),
3186 "192.168.1.10:5000".parse().unwrap(),
3187 );
3188 assert!(
3189 result.is_err(),
3190 "non-loopback with unspecified bind should fail"
3191 );
3192 }
3193
3194 #[test]
3195 fn compute_advertise_ipv6_loopback() {
3196 let result =
3197 compute_advertise_ip(None, "::".parse().unwrap(), "[::1]:5000".parse().unwrap());
3198 assert_eq!(
3199 result.unwrap(),
3200 std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST)
3201 );
3202 }
3203
3204 #[test]
3205 fn compute_advertise_explicit_overrides_bind() {
3206 let result = compute_advertise_ip(
3207 Some("10.0.0.1".parse().unwrap()),
3208 "192.168.1.1".parse().unwrap(),
3209 "127.0.0.1:5000".parse().unwrap(),
3210 );
3211 assert_eq!(
3212 result.unwrap(),
3213 std::net::IpAddr::V4("10.0.0.1".parse().unwrap())
3214 );
3215 }
3216}