1use std::collections::HashMap;
9use std::fmt;
10use std::net::IpAddr;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::{Arc, Mutex, MutexGuard};
13use std::time::{Duration, Instant};
14
15static AUTH_CACHE_EPOCH: std::sync::LazyLock<Instant> = std::sync::LazyLock::new(Instant::now);
16
17use eggress_core::BoxStream;
18use eggress_core::{ClientIdentity, ProtocolId, TargetAddr};
19use tokio::io::AsyncReadExt;
20use zeroize::Zeroize;
21
22pub(crate) mod detect;
23pub(crate) mod forward;
24pub(crate) mod handlers;
25pub(crate) mod prefixed;
26#[cfg(test)]
27mod tests;
28
29pub(crate) use detect::{detect_http_method, DetectResult};
30#[cfg(test)]
31pub(crate) use forward::read_http_head;
32pub(crate) use handlers::{accept_http, accept_socks4, accept_socks5};
33pub(crate) use prefixed::PrefixedStream;
34
35pub struct AuthReuseCache {
42 timeout: Duration,
43 entries: Mutex<HashMap<IpAddr, AuthReuseEntry>>,
44 max_entries: usize,
45 last_sweep_nanos: AtomicU64,
49}
50
51struct AuthReuseEntry {
52 identity: ClientIdentity,
53 last_authenticated: Instant,
54}
55
56impl AuthReuseCache {
57 pub const DEFAULT_MAX_ENTRIES: usize = 4096;
58
59 const SWEEP_INTERVAL: Duration = Duration::from_secs(30);
63
64 pub fn new(timeout: Duration) -> Self {
65 Self {
66 timeout,
67 entries: Mutex::new(HashMap::new()),
68 max_entries: Self::DEFAULT_MAX_ENTRIES,
69 last_sweep_nanos: AtomicU64::new(0),
70 }
71 }
72
73 fn lock_entries(&self) -> MutexGuard<'_, HashMap<IpAddr, AuthReuseEntry>> {
74 self.entries.lock().unwrap_or_else(|error| {
75 tracing::warn!("auth reuse cache was poisoned; clearing it: {error}");
76 let mut entries = error.into_inner();
77 entries.clear();
78 self.entries.clear_poison();
79 entries
80 })
81 }
82
83 pub fn lookup(&self, peer_ip: IpAddr) -> Option<ClientIdentity> {
84 let mut entries = self.lock_entries();
85 let entry = entries.get(&peer_ip)?;
86 if Instant::now().duration_since(entry.last_authenticated) > self.timeout {
87 entries.remove(&peer_ip);
88 return None;
89 }
90 Some(entry.identity.clone())
91 }
92
93 pub fn record(&self, peer_ip: IpAddr, identity: ClientIdentity) {
94 let mut entries = self.lock_entries();
95 let now = Instant::now();
96 if entries.len() >= self.max_entries {
103 let now_nanos = u64::try_from(
104 now.checked_duration_since(*AUTH_CACHE_EPOCH)
105 .unwrap_or(Duration::ZERO)
106 .as_nanos(),
107 )
108 .unwrap_or(u64::MAX);
109 let last_sweep = self.last_sweep_nanos.load(Ordering::Acquire);
110 let interval_nanos = u64::try_from(Self::SWEEP_INTERVAL.as_nanos()).unwrap_or(u64::MAX);
111 if now_nanos.saturating_sub(last_sweep) >= interval_nanos {
112 self.last_sweep_nanos.store(now_nanos, Ordering::Release);
113 entries.retain(|_, entry| {
114 now.duration_since(entry.last_authenticated) <= self.timeout
115 });
116 }
117 }
118 if entries.len() >= self.max_entries && !entries.contains_key(&peer_ip) {
119 if let Some(oldest) = entries
120 .iter()
121 .min_by_key(|(_, entry)| entry.last_authenticated)
122 .map(|(ip, _)| *ip)
123 {
124 entries.remove(&oldest);
125 }
126 }
127 entries.insert(
128 peer_ip,
129 AuthReuseEntry {
130 identity,
131 last_authenticated: now,
132 },
133 );
134 }
135
136 pub fn len(&self) -> usize {
137 self.lock_entries().len()
138 }
139
140 pub fn is_empty(&self) -> bool {
141 self.len() == 0
142 }
143}
144
145#[derive(Clone)]
146pub enum InboundAuthentication {
147 None,
148 UsernamePassword {
149 username: String,
150 password: String,
151 },
152 UsernamePasswordWithReuse {
153 username: String,
154 password: String,
155 reuse: Arc<AuthReuseCache>,
156 },
157}
158
159impl Drop for InboundAuthentication {
160 fn drop(&mut self) {
161 match self {
162 Self::None => {}
163 Self::UsernamePassword { password, .. }
164 | Self::UsernamePasswordWithReuse { password, .. } => password.zeroize(),
165 }
166 }
167}
168
169impl fmt::Debug for InboundAuthentication {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 match self {
172 InboundAuthentication::None => write!(f, "InboundAuthentication::None"),
173 InboundAuthentication::UsernamePassword { .. } => {
174 write!(f, "InboundAuthentication::UsernamePassword {{ .. }}")
175 }
176 InboundAuthentication::UsernamePasswordWithReuse { .. } => write!(
177 f,
178 "InboundAuthentication::UsernamePasswordWithReuse {{ .. }}"
179 ),
180 }
181 }
182}
183
184impl fmt::Display for InboundAuthentication {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match self {
187 InboundAuthentication::None => write!(f, "none"),
188 InboundAuthentication::UsernamePassword { .. } => write!(f, "username/password"),
189 InboundAuthentication::UsernamePasswordWithReuse { .. } => {
190 write!(f, "username/password with IP reuse")
191 }
192 }
193 }
194}
195
196pub(crate) fn auth_credentials(
197 auth: &InboundAuthentication,
198) -> Option<(&str, &str, Option<&AuthReuseCache>)> {
199 match auth {
200 InboundAuthentication::None => None,
201 InboundAuthentication::UsernamePassword { username, password } => {
202 Some((username, password, None))
203 }
204 InboundAuthentication::UsernamePasswordWithReuse {
205 username,
206 password,
207 reuse,
208 } => Some((username, password, Some(reuse))),
209 }
210}
211
212pub(crate) fn cached_identity(
213 auth: &InboundAuthentication,
214 peer_ip: Option<IpAddr>,
215) -> Option<ClientIdentity> {
216 let (_, _, reuse) = auth_credentials(auth)?;
217 peer_ip.and_then(|ip| reuse.and_then(|cache| cache.lookup(ip)))
218}
219
220pub(crate) fn record_authenticated(
221 auth: &InboundAuthentication,
222 peer_ip: Option<IpAddr>,
223 identity: &ClientIdentity,
224) {
225 let Some((_, _, Some(cache))) = auth_credentials(auth) else {
226 return;
227 };
228 if let Some(ip) = peer_ip {
229 cache.record(ip, identity.clone());
230 }
231}
232
233#[derive(Debug, thiserror::Error)]
235
236pub enum AcceptError {
237 #[error("protocol error")]
238 Protocol(#[source] Box<dyn std::error::Error + Send + Sync>),
239 #[error("authentication failed")]
240 AuthenticationFailed,
241}
242
243impl From<Box<dyn std::error::Error + Send + Sync>> for AcceptError {
244 fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
245 AcceptError::Protocol(e)
246 }
247}
248
249pub enum AcceptedSession {
251 Tunnel(PendingTunnel),
252 HttpForward(PendingHttpForward),
253 UdpAssociate(PendingUdpAssociate),
254 Echo(BoxStream),
255}
256
257pub struct PendingTunnel {
260 pub target: TargetAddr,
261 pub client: BoxStream,
262 pub protocol: TunnelProtocol,
263 pub reply_context: ReplyContext,
264 pub identity: ClientIdentity,
265}
266
267pub struct PendingHttpForward {
269 pub target: TargetAddr,
270 pub client: BoxStream,
271 pub request: eggress_protocol_http::forward::ForwardRequest,
272 pub identity: ClientIdentity,
273}
274
275pub struct PendingUdpAssociate {
277 pub client: BoxStream,
278 pub protocol: TunnelProtocol,
279 pub identity: ClientIdentity,
280 pub client_hint: Option<TargetAddr>,
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum TunnelProtocol {
286 HttpConnect,
287 Http2,
288 Http3,
289 WebSocket,
290 Socks4,
291 Socks5,
292 Shadowsocks,
293 ShadowsocksR,
294 Trojan,
295 Raw,
296}
297
298pub enum ReplyContext {
300 Http,
301 Http2,
302 Http3,
303 WebSocket,
304 Socks4,
305 Socks5,
306 Shadowsocks,
307 Trojan,
308 Raw,
309}
310
311#[derive(Clone)]
313pub struct InboundShadowsocksConfig {
314 pub method: String,
315 pub password: String,
316 #[cfg(feature = "pproxy-legacy")]
317 pub auth_prefix: Option<Vec<u8>>,
318 #[cfg(feature = "pproxy-legacy")]
319 pub plugins: Vec<String>,
320}
321
322impl Drop for InboundShadowsocksConfig {
323 fn drop(&mut self) {
324 self.password.zeroize();
325 }
326}
327
328impl fmt::Debug for InboundShadowsocksConfig {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 let mut ds = f.debug_struct("InboundShadowsocksConfig");
331 ds.field("method", &self.method);
332 ds.field("password", &"***");
333 #[cfg(feature = "pproxy-legacy")]
334 {
335 ds.field("auth_prefix", &self.auth_prefix);
336 ds.field("plugins", &self.plugins);
337 }
338 ds.finish()
339 }
340}
341
342#[derive(Clone)]
344pub struct InboundTrojanConfig {
345 pub password: String,
346 pub fallback: Option<String>,
350}
351
352impl Drop for InboundTrojanConfig {
353 fn drop(&mut self) {
354 self.password.zeroize();
355 }
356}
357
358impl fmt::Debug for InboundTrojanConfig {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 f.debug_struct("InboundTrojanConfig")
361 .field("password", &"***")
362 .field("fallback", &self.fallback)
363 .finish()
364 }
365}
366
367pub async fn accept(
371 client: BoxStream,
372 protocols: &[ProtocolId],
373 auth: &InboundAuthentication,
374 shadowsocks_config: Option<&InboundShadowsocksConfig>,
375 #[cfg(feature = "extended")] shadowsocks_metrics: Option<
376 &std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
377 >,
378 #[cfg(not(feature = "extended"))] shadowsocks_metrics: Option<&()>,
379 trojan_config: Option<&InboundTrojanConfig>,
380) -> Result<AcceptedSession, AcceptError> {
381 #[cfg(not(feature = "extended"))]
382 let _ = (shadowsocks_config, shadowsocks_metrics, trojan_config);
383 accept_with_fixed_target(
384 client,
385 protocols,
386 auth,
387 shadowsocks_config,
388 shadowsocks_metrics,
389 trojan_config,
390 None,
391 )
392 .await
393}
394
395pub async fn accept_with_fixed_target(
396 client: BoxStream,
397 protocols: &[ProtocolId],
398 auth: &InboundAuthentication,
399 shadowsocks_config: Option<&InboundShadowsocksConfig>,
400 #[cfg(feature = "extended")] shadowsocks_metrics: Option<
401 &std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
402 >,
403 #[cfg(not(feature = "extended"))] shadowsocks_metrics: Option<&()>,
404 trojan_config: Option<&InboundTrojanConfig>,
405 fixed_target: Option<&TargetAddr>,
406) -> Result<AcceptedSession, AcceptError> {
407 accept_with_fixed_target_for_peer(
408 client,
409 protocols,
410 auth,
411 shadowsocks_config,
412 shadowsocks_metrics,
413 trojan_config,
414 fixed_target,
415 None,
416 )
417 .await
418}
419
420#[allow(clippy::too_many_arguments)]
421pub async fn accept_with_fixed_target_for_peer(
422 client: BoxStream,
423 protocols: &[ProtocolId],
424 auth: &InboundAuthentication,
425 shadowsocks_config: Option<&InboundShadowsocksConfig>,
426 #[cfg(feature = "extended")] shadowsocks_metrics: Option<
427 &std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
428 >,
429 #[cfg(not(feature = "extended"))] shadowsocks_metrics: Option<&()>,
430 trojan_config: Option<&InboundTrojanConfig>,
431 fixed_target: Option<&TargetAddr>,
432 peer_ip: Option<IpAddr>,
433) -> Result<AcceptedSession, AcceptError> {
434 #[cfg(not(feature = "extended"))]
435 let _ = (shadowsocks_config, shadowsocks_metrics, trojan_config);
436 #[cfg(feature = "extended")]
437 #[inline]
438 fn shadows_metrics(
439 m: Option<&std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>>,
440 ) -> Option<std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>> {
441 m.cloned()
442 }
443 let mut stream = client;
444 if protocols.len() == 1 && protocols.contains(&ProtocolId::Echo) {
445 return Ok(AcceptedSession::Echo(stream));
446 }
447 if protocols.len() == 1 && protocols.contains(&ProtocolId::Raw) {
448 let target = fixed_target
449 .cloned()
450 .ok_or_else(|| AcceptError::Protocol("raw listener requires fixed_target".into()))?;
451 return Ok(AcceptedSession::Tunnel(PendingTunnel {
452 target,
453 client: stream,
454 protocol: TunnelProtocol::Raw,
455 reply_context: ReplyContext::Raw,
456 identity: ClientIdentity::Anonymous,
457 }));
458 }
459 let mut first_byte = [0u8; 1];
460 stream
461 .read_exact(&mut first_byte)
462 .await
463 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
464
465 let has_socks5 = protocols.contains(&ProtocolId::Socks5);
466 let has_socks4 = protocols.contains(&ProtocolId::Socks4);
467 let has_http = protocols.contains(&ProtocolId::Http);
468
469 if first_byte[0] == 0x05 && has_socks5 {
471 tracing::trace!(
472 "detected protocol: socks5 (first_byte={:#04x})",
473 first_byte[0]
474 );
475 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
476 return accept_socks5(stream, auth, peer_ip).await;
477 }
478
479 if first_byte[0] == 0x04 && has_socks4 {
481 tracing::trace!(
482 "detected protocol: socks4 (first_byte={:#04x})",
483 first_byte[0]
484 );
485 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
486 return accept_socks4(stream, auth, peer_ip).await;
487 }
488
489 if has_http {
491 let mut prefix = vec![first_byte[0]];
493 let mut buf = [0u8; 32];
494 let n = stream
495 .read(&mut buf)
496 .await
497 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
498 prefix.extend_from_slice(&buf[..n]);
499
500 match detect_http_method(&prefix) {
501 DetectResult::Match => {
502 tracing::trace!(
503 "detected protocol: http (prefix={:?})",
504 &prefix[..prefix.len().min(16)]
505 );
506 let stream: BoxStream = Box::new(PrefixedStream::new(prefix, stream));
507 return accept_http(stream, auth, peer_ip).await;
508 }
509 DetectResult::NeedMore => {
510 let mut more = [0u8; 32];
512 let n = stream
513 .read(&mut more)
514 .await
515 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
516 prefix.extend_from_slice(&more[..n]);
517 match detect_http_method(&prefix) {
518 DetectResult::Match => {
519 tracing::trace!(
520 "detected protocol: http (prefix={:?})",
521 &prefix[..prefix.len().min(16)]
522 );
523 let stream: BoxStream = Box::new(PrefixedStream::new(prefix, stream));
524 return accept_http(stream, auth, peer_ip).await;
525 }
526 DetectResult::NoMatch => {
527 return Err(AcceptError::Protocol(
528 "no matching protocol for listener".into(),
529 ));
530 }
531 DetectResult::NeedMore => {
532 return Err(AcceptError::Protocol(
533 "no matching protocol for listener".into(),
534 ));
535 }
536 }
537 }
538 DetectResult::NoMatch => {
539 return Err(AcceptError::Protocol(
540 "no matching protocol for listener".into(),
541 ));
542 }
543 }
544 }
545
546 #[cfg(feature = "extended")]
548 if protocols.len() == 1 && protocols.contains(&ProtocolId::Shadowsocks) {
549 if let Some(ss_config) = shadowsocks_config {
550 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
551 match eggress_protocol_shadowsocks::CipherMethod::parse_method(&ss_config.method) {
552 Ok(method) => {
553 let (ss_stream, target_addr) =
554 eggress_protocol_shadowsocks::tcp::shadowsocks_accept(
555 stream,
556 &ss_config.password,
557 method,
558 shadows_metrics(shadowsocks_metrics),
559 )
560 .await
561 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
562
563 return Ok(AcceptedSession::Tunnel(PendingTunnel {
564 target: target_addr,
565 client: ss_stream,
566 protocol: TunnelProtocol::Shadowsocks,
567 reply_context: ReplyContext::Shadowsocks,
568 identity: ClientIdentity::Anonymous,
569 }));
570 }
571 Err(modern_error) => {
572 #[cfg(feature = "legacy-crypto")]
573 if let Ok(legacy_method) =
574 eggress_protocol_shadowsocks::legacy::LegacyMethod::parse(&ss_config.method)
575 {
576 let (ss_stream, target_addr) =
577 eggress_protocol_shadowsocks::legacy::legacy_accept(
578 stream,
579 legacy_method,
580 ss_config.password.as_bytes(),
581 )
582 .await
583 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
584
585 return Ok(AcceptedSession::Tunnel(PendingTunnel {
586 target: target_addr,
587 client: ss_stream,
588 protocol: TunnelProtocol::Shadowsocks,
589 reply_context: ReplyContext::Shadowsocks,
590 identity: ClientIdentity::Anonymous,
591 }));
592 }
593 if let Some(m) = shadowsocks_metrics {
594 m.record_tcp_unsupported_method_reject();
595 }
596 return Err(AcceptError::Protocol(Box::new(modern_error)));
597 }
598 }
599 }
600 return Err(AcceptError::Protocol(
601 "shadowsocks listener requires shadowsocks config".into(),
602 ));
603 }
604
605 #[cfg(feature = "pproxy-legacy")]
606 if protocols.len() == 1 && protocols.contains(&ProtocolId::ShadowsocksR) {
607 let ssr_config = shadowsocks_config
608 .filter(|config| config.method == "ssr")
609 .ok_or_else(|| AcceptError::Protocol("SSR listener requires SSR config".into()))?;
610 let plugins =
611 eggress_protocol_shadowsocks::compat::plugin::parse_plugins(&ssr_config.plugins)
612 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
613 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
614 let (ss_stream, target_addr) = eggress_protocol_shadowsocks::compat::ssr::ssr_accept(
615 stream,
616 &eggress_protocol_shadowsocks::compat::ssr::SsrConfig {
617 auth_prefix: ssr_config.auth_prefix.clone(),
618 plugins,
619 },
620 )
621 .await
622 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
623 return Ok(AcceptedSession::Tunnel(PendingTunnel {
624 target: target_addr,
625 client: ss_stream,
626 protocol: TunnelProtocol::ShadowsocksR,
627 reply_context: ReplyContext::Shadowsocks,
628 identity: ClientIdentity::Anonymous,
629 }));
630 }
631 #[cfg(not(feature = "pproxy-legacy"))]
632 if protocols.len() == 1 && protocols.contains(&ProtocolId::ShadowsocksR) {
633 return Err(AcceptError::Protocol(
634 "SSR compatibility support is not included in this build".into(),
635 ));
636 }
637 #[cfg(not(feature = "extended"))]
638 if protocols.len() == 1 && protocols.contains(&ProtocolId::Shadowsocks) {
639 return Err(AcceptError::Protocol(
640 "shadowsocks support not included in this build".into(),
641 ));
642 }
643
644 #[cfg(feature = "extended")]
646 if protocols.len() == 1 && protocols.contains(&ProtocolId::Trojan) {
647 if let Some(trojan_cfg) = trojan_config {
648 use tokio::io::AsyncReadExt;
649
650 let mut hash_prefix = [0u8; 56];
654 hash_prefix[0] = first_byte[0];
658 stream
659 .read_exact(&mut hash_prefix[1..])
660 .await
661 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
662
663 let password_matches =
664 eggress_protocol_trojan::trojan_check_password(&hash_prefix, &trojan_cfg.password);
665
666 if password_matches {
667 let prefixed = PrefixedStream::new(hash_prefix.to_vec(), stream);
669 let boxed: BoxStream = Box::new(prefixed);
670 let (trojan_stream, result) =
671 eggress_protocol_trojan::trojan_accept(boxed, &trojan_cfg.password)
672 .await
673 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
674
675 return Ok(AcceptedSession::Tunnel(PendingTunnel {
676 target: result.target,
677 client: trojan_stream,
678 protocol: TunnelProtocol::Trojan,
679 reply_context: ReplyContext::Trojan,
680 identity: ClientIdentity::Anonymous,
681 }));
682 }
683
684 if let Some(ref fallback_target) = trojan_cfg.fallback {
686 let mut delimiter = [0u8; 2];
691 stream
692 .read_exact(&mut delimiter)
693 .await
694 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
695 if delimiter != *b"\r\n" {
696 tracing::warn!(?delimiter, "trojan fallback delimiter was not CRLF");
697 }
698 let target: TargetAddr = fallback_target.parse().map_err(|e: String| {
699 AcceptError::Protocol(format!("invalid trojan fallback address: {e}").into())
700 })?;
701 tracing::debug!("trojan auth failed, falling back to {}", fallback_target);
702 return Ok(AcceptedSession::Tunnel(PendingTunnel {
703 target,
704 client: stream,
705 protocol: TunnelProtocol::Trojan,
706 reply_context: ReplyContext::Trojan,
707 identity: ClientIdentity::Anonymous,
708 }));
709 }
710
711 return Err(AcceptError::AuthenticationFailed);
712 }
713 return Err(AcceptError::Protocol(
714 "trojan listener requires trojan config".into(),
715 ));
716 }
717 #[cfg(not(feature = "extended"))]
718 if protocols.len() == 1 && protocols.contains(&ProtocolId::Trojan) {
719 return Err(AcceptError::Protocol(
720 "trojan support not included in this build".into(),
721 ));
722 }
723
724 Err(AcceptError::Protocol(
725 "no matching protocol for listener".into(),
726 ))
727}