diff --git a/crates/tuic-client/src/config.rs b/crates/tuic-client/src/config.rs index a2e64ce..35064d2 100644 --- a/crates/tuic-client/src/config.rs +++ b/crates/tuic-client/src/config.rs @@ -230,10 +230,8 @@ fn default_udp_timeout() -> Duration { impl Config { pub fn parse(cli: Cli, env_state: EnvState) -> eyre::Result { - // Require config file let path = cli.config.ok_or(ConfigError::NoConfig)?; - // Check if config file exists if !path.exists() { return Err(ConfigError::ConfigNotFound(path))?; } @@ -254,7 +252,6 @@ impl Config { _ => format = ConfigFormat::Unknown, } } else { - // Fall back to file extension match path .extension() .and_then(|v| v.to_str()) @@ -274,7 +271,6 @@ impl Config { ConfigFormat::Toml => figmet.merge(Toml::file(&path)), ConfigFormat::Yaml => figmet.merge(Yaml::file(&path)), ConfigFormat::Unknown => { - // Try to infer format from file content let content = std::fs::read_to_string(&path)?; let inferred_format = infer_config_format(&content); @@ -306,7 +302,6 @@ enum ConfigFormat { fn infer_config_format(content: &str) -> ConfigFormat { let trimmed = content.trim(); - // Check for YAML indicators if trimmed.lines().any(|line| { let line = line.trim(); // YAML typically has keys followed by colons (not in quotes) @@ -322,7 +317,6 @@ fn infer_config_format(content: &str) -> ConfigFormat { return ConfigFormat::Yaml; } - // Check for TOML indicators (section headers) if trimmed.lines().any(|line| { let line = line.trim(); line.starts_with('[') && line.ends_with(']') && !line.contains('{') @@ -330,7 +324,6 @@ fn infer_config_format(content: &str) -> ConfigFormat { return ConfigFormat::Toml; } - // Check for JSON/JSON5 indicators if trimmed.starts_with('{') || trimmed.starts_with('[') { return ConfigFormat::Json; } @@ -454,22 +447,18 @@ mod tests { fs::write(&config_path, config_content).unwrap(); - // Temporarily set command line arguments for clap to parse let os_args = vec![ "test_binary".to_owned(), "--config".to_owned(), config_path.to_string_lossy().into_owned(), ]; - // Parse CLI with test arguments let cli = Cli::try_parse_from(os_args).map_err(|e| ConfigError::Figment(figment::Error::from(e.to_string())))?; - // Call parse with the CLI and env_state Config::parse(cli, env_state) } #[test] fn test_backward_compatibility_standard_json() { - // Test backward compatibility with standard JSON format let json_config = include_str!("../tests/config/backward_compatibility_standard_json.json"); let config = test_parse_config(json_config, ".json5"); @@ -483,7 +472,6 @@ mod tests { #[test] fn test_json5_comments() { - // Test JSON5 comment support (single-line and multi-line) let json5_config = include_str!("../tests/config/json5_comments.json5"); let config = test_parse_config(json5_config, ".json5"); @@ -492,7 +480,6 @@ mod tests { #[test] fn test_json5_trailing_commas() { - // Test JSON5 trailing comma support let json5_config = include_str!("../tests/config/json5_trailing_commas.json5"); let config = test_parse_config(json5_config, ".json5"); @@ -501,7 +488,6 @@ mod tests { #[test] fn test_json5_unquoted_keys() { - // Test JSON5 unquoted object keys let json5_config = include_str!("../tests/config/json5_unquoted_keys.json5"); let config = test_parse_config(json5_config, ".json5"); @@ -510,7 +496,6 @@ mod tests { #[test] fn test_json5_single_quotes() { - // Test JSON5 single-quoted strings let json5_config = include_str!("../tests/config/json5_single_quotes.json5"); let config = test_parse_config(json5_config, ".json5"); @@ -519,7 +504,6 @@ mod tests { #[test] fn test_json5_multiline_strings() { - // Test JSON5 multiline strings with escaped newlines let json5_config = include_str!("../tests/config/json5_multiline_strings.json5"); let config = test_parse_config(json5_config, ".json5"); @@ -528,7 +512,6 @@ mod tests { #[test] fn test_json5_mixed_features() { - // Test multiple JSON5 features combined let json5_config = include_str!("../tests/config/json5_mixed_features.json5"); let config = test_parse_config(json5_config, ".json5"); @@ -540,7 +523,6 @@ mod tests { #[test] fn test_complex_config_with_all_fields() { - // Test a more complete configuration with various optional fields let json5_config = include_str!("../tests/config/complex_config_with_all_fields.json5"); let config = test_parse_config(json5_config, ".json5"); @@ -557,7 +539,6 @@ mod tests { let config = test_parse_config(json5_config, ".json5").unwrap(); - // Check default values assert_eq!(config.log_level, "info"); assert_eq!(config.relay.ipstack_prefer, StackPrefer::V4first); assert_eq!(config.relay.udp_relay_mode, UdpRelayMode::Native); @@ -680,10 +661,8 @@ server = "127.0.0.1:1081" let proxy = config.relay.proxy.unwrap(); assert_eq!(proxy.server.0, "proxy.example.com"); assert_eq!(proxy.server.1, 1080); - // username and password should be None when not provided assert!(proxy.username.is_none()); assert!(proxy.password.is_none()); - // Should use default udp_buffer_size assert_eq!(proxy.udp_buffer_size, 2048); } @@ -697,7 +676,6 @@ server = "127.0.0.1:1081" assert_eq!(proxy.server.1, 1080); assert!(proxy.username.is_none()); assert!(proxy.password.is_none()); - // Default udp_buffer_size should be 2048 assert_eq!(proxy.udp_buffer_size, 2048); } @@ -706,7 +684,6 @@ server = "127.0.0.1:1081" let toml_config = include_str!("../tests/config/no_proxy.toml"); let config = test_parse_config(toml_config, ".toml").unwrap(); - // proxy should be None when not configured assert!(config.relay.proxy.is_none()); } @@ -748,7 +725,6 @@ server = "127.0.0.1:1081" let config = test_parse_config(toml_config, ".toml").unwrap(); - // Test default values assert_eq!(config.log_level, "info"); assert_eq!(config.relay.congestion_control, CongestionControl::Bbr); assert_eq!(config.relay.udp_relay_mode, UdpRelayMode::Native); @@ -874,7 +850,6 @@ server = "127.0.0.1:1081" fn test_env_var_force_toml() { let config_content = include_str!("../tests/config/env_var_force_toml.toml"); - // Create EnvState with force_toml enabled let env_state = EnvState { tuic_force_toml: true, tuic_config_format: None, @@ -889,7 +864,6 @@ server = "127.0.0.1:1081" fn test_env_var_config_format() { let config_content = include_str!("../tests/config/env_yaml.toml"); - // Create EnvState with config_format set to YAML let env_state = EnvState { tuic_force_toml: false, tuic_config_format: Some("yaml".to_string()), @@ -927,12 +901,10 @@ server = "127.0.0.1:1081" #[test] fn test_backward_compat_json_to_toml() { - // Test that configs can be converted from JSON5 to TOML let json5_content = include_str!("../tests/config/compat_json.json5"); let json_config = test_parse_config(json5_content, ".json5").unwrap(); - // Verify the config is parsed correctly assert_eq!(json_config.relay.server.0, "compat.example.com"); assert_eq!(json_config.relay.server.1, 8443); assert_eq!(json_config.log_level, "warn"); diff --git a/crates/tuic-client/src/forward.rs b/crates/tuic-client/src/forward.rs index e96e138..245e600 100644 --- a/crates/tuic-client/src/forward.rs +++ b/crates/tuic-client/src/forward.rs @@ -183,7 +183,6 @@ async fn run_udp_forwarder(entry: UdpForward, cancel: CancellationToken) { let pkt = Bytes::copy_from_slice(&buf[..n]); let target = TargetAddr::Domain(entry.remote.0.clone(), entry.remote.1); - // Look up or create the session for this source. let session = sessions.entry(src_addr).or_insert_with(|| { let assoc_id = next_assoc_id(); let socket_for_reply = socket.clone(); diff --git a/crates/tuic-client/src/socks5/handle_task.rs b/crates/tuic-client/src/socks5/handle_task.rs index 53c54ed..f3ca6ae 100644 --- a/crates/tuic-client/src/socks5/handle_task.rs +++ b/crates/tuic-client/src/socks5/handle_task.rs @@ -109,7 +109,6 @@ impl Server { } }; - // Spawn wind outbound UDP handler let outbound_handle = tokio::spawn(async move { match get_connection() { Some(adapter) => { @@ -149,7 +148,6 @@ impl Server { // session is gone either way. let _ = UDP_SESSIONS.get().unwrap().write().await.remove(&assoc_id); - // Cancel the outbound UDP handler outbound_handle.abort(); } Err(err) => { diff --git a/crates/tuic-client/src/socks5/udp_session.rs b/crates/tuic-client/src/socks5/udp_session.rs index adeee9b..e561604 100644 --- a/crates/tuic-client/src/socks5/udp_session.rs +++ b/crates/tuic-client/src/socks5/udp_session.rs @@ -175,10 +175,6 @@ impl UdpSession { } } -// --------------------------------------------------------------------------- -// PR1 regression tests -// --------------------------------------------------------------------------- - #[cfg(test)] mod tests { use std::net::{Ipv4Addr, SocketAddr}; diff --git a/crates/tuic-client/src/utils.rs b/crates/tuic-client/src/utils.rs index c5c7c1b..279c838 100644 --- a/crates/tuic-client/src/utils.rs +++ b/crates/tuic-client/src/utils.rs @@ -7,7 +7,6 @@ use std::{ use anyhow::Context; use rustls::{RootCertStore, pki_types::CertificateDer}; use tokio::net; -// Re-export common types from wind-core and wind-tuic pub use wind_core::StackPrefer; pub use wind_tuic::quinn::{CongestionControl, UdpRelayMode}; diff --git a/crates/tuic-client/src/wind_adapter.rs b/crates/tuic-client/src/wind_adapter.rs index 27d2cbe..5971c5f 100644 --- a/crates/tuic-client/src/wind_adapter.rs +++ b/crates/tuic-client/src/wind_adapter.rs @@ -11,7 +11,6 @@ use wind_tuic::quinn::outbound::{TuicOutbound, TuicOutboundOpts}; use crate::config::Relay; -// Global wind-tuic connection static WIND_CONNECTION: OnceCell = OnceCell::new(); /// Wind-tuic outbound wrapper for tuic-client @@ -21,11 +20,9 @@ pub struct TuicOutboundAdapter { impl TuicOutboundAdapter { pub async fn new(ctx: Arc, relay: Relay) -> eyre::Result { - // Parse server address let server_addr = if let Some(ip) = relay.ip { SocketAddr::new(ip, relay.server.1) } else { - // Resolve domain let addrs = tokio::net::lookup_host(format!("{}:{}", relay.server.0, relay.server.1)).await?; addrs .into_iter() @@ -33,7 +30,6 @@ impl TuicOutboundAdapter { .ok_or_else(|| eyre::eyre!("Failed to resolve server address"))? }; - // Convert password to Arc<[u8]> let password: Arc<[u8]> = relay.password.clone(); // Pick the SNI to send during TLS handshake. @@ -66,7 +62,6 @@ impl TuicOutboundAdapter { } }; - // Create wind-tuic outbound options let opts = TuicOutboundOpts { peer_addr: server_addr, sni, @@ -83,10 +78,8 @@ impl TuicOutboundAdapter { .collect(), }; - // Create outbound let outbound: TuicOutbound = TuicOutbound::new(ctx, opts).await?; - // Start polling outbound.start_poll().await?; Ok(Self { outbound }) diff --git a/crates/tuic-core/src/proto/addr.rs b/crates/tuic-core/src/proto/addr.rs index b084d03..7786034 100644 --- a/crates/tuic-core/src/proto/addr.rs +++ b/crates/tuic-core/src/proto/addr.rs @@ -7,10 +7,6 @@ use wind_core::types::TargetAddr; use crate::proto::{BytesRemainingSnafu, DomainTooLongSnafu, ProtoError}; -//----------------------------------------------------------------------------- -// Type Definitions -//----------------------------------------------------------------------------- - /// Codec for TUIC address encoding and decoding #[derive(Debug, Clone, Copy)] pub struct AddressCodec; @@ -40,10 +36,6 @@ pub enum AddressType { Other(u8), } -//----------------------------------------------------------------------------- -// Implementations -//----------------------------------------------------------------------------- - impl From for Address { fn from(value: TargetAddr) -> Self { match value { @@ -54,10 +46,6 @@ impl From for Address { } } -//----------------------------------------------------------------------------- -// Codec Implementation -//----------------------------------------------------------------------------- - /// Implementation according to TUIC specification: /// https://github.com/proxy-rs/wind/blob/main/crates/wind-tuic/SPEC.md#6-address-encoding #[cfg(feature = "decode")] @@ -111,7 +99,6 @@ impl Encoder
for AddressCodec { dst.put_u16(port); } Address::Domain(domain, port) => { - // Validate domain length if domain.len() > u8::MAX as usize { return DomainTooLongSnafu { domain }.fail(); } @@ -128,10 +115,6 @@ impl Encoder
for AddressCodec { } } -//----------------------------------------------------------------------------- -// Tests -//----------------------------------------------------------------------------- - #[cfg(test)] mod test { use std::net::{Ipv4Addr, Ipv6Addr}; @@ -154,7 +137,6 @@ mod test { Address::Domain(String::from("www.google.com"), 443), ]; - // Test encoding let mut writer = FramedWrite::new(buffer, AddressCodec); let mut expect_len = 0; for var in &vars { @@ -168,7 +150,6 @@ mod test { assert_eq!(writer.get_ref().len(), expect_len); } - // Test decoding let buffer = writer.get_ref(); let mut reader = FramedRead::new(buffer.as_slice(), AddressCodec); for var in vars { @@ -188,7 +169,6 @@ mod test { ]; for addr in vars { - // Encode the address let buffer = Vec::with_capacity(128); let mut writer = FramedWrite::new(buffer, AddressCodec); writer.send(addr.clone()).await?; diff --git a/crates/tuic-core/src/proto/error.rs b/crates/tuic-core/src/proto/error.rs index 6ac68d6..6dee47b 100644 --- a/crates/tuic-core/src/proto/error.rs +++ b/crates/tuic-core/src/proto/error.rs @@ -33,7 +33,6 @@ pub enum ProtoError { // Caller should yield BytesRemaining, Io { - // #[snafu(backtrace)] source: std::io::Error, backtrace: Backtrace, }, diff --git a/crates/tuic-core/src/udp.rs b/crates/tuic-core/src/udp.rs index b768c6c..35e9f4a 100644 --- a/crates/tuic-core/src/udp.rs +++ b/crates/tuic-core/src/udp.rs @@ -112,7 +112,7 @@ impl FragmentReassemblyBuffer { let key = (assoc_id, pkt_id); - // Check if this is a placeholder address (used for non-first fragments) + // Placeholder address used for non-first fragments. let is_placeholder_addr = matches!(target, TargetAddr::IPv4(ip, 0) if ip.is_unspecified()); // Wrap in Arc once so both the `or_insert_with` future and the // post-lookup `store` path can share a reference without cloning the @@ -120,7 +120,6 @@ impl FragmentReassemblyBuffer { let target_arc = Arc::new(target); let source_arc = source.map(Arc::new); - // Get or create the fragment metadata let is_complete = { let meta = self .fragments @@ -164,27 +163,22 @@ impl FragmentReassemblyBuffer { meta.value().target.store(target_arc); } - // Update timestamp meta.value() .last_updated .store(init_time().elapsed().as_secs(), Ordering::Relaxed); - // Store this fragment meta.value().fragments.insert(frag_id, payload).await; - // Ensure all pending cache operations are completed meta.value().fragments.run_pending_tasks().await; - // Check if all fragments have been received meta.value().fragments.entry_count() == meta.value().frag_total as u64 }; if is_complete { - // All fragments received, reassemble the packet return self.reassemble_packet(key).await; } - None // Not all fragments received yet + None } /// Clean up expired fragments. @@ -206,7 +200,6 @@ impl FragmentReassemblyBuffer { /// Reassemble a complete packet from fragments. async fn reassemble_packet(&self, key: (u16, u16)) -> Option { if let Some(meta) = self.fragments.remove(&key).await { - // Create a buffer to hold the reassembled packet let mut total_size = 0; for i in 0..meta.frag_total { let fragment = meta.fragments.get(&i).await?; @@ -214,13 +207,12 @@ impl FragmentReassemblyBuffer { } let mut buffer = BytesMut::with_capacity(total_size); - // Combine fragments in order + // Combine fragments in order. for i in 0..meta.frag_total { let fragment = meta.fragments.get(&i).await?; buffer.put_slice(&fragment); } - // Return the reassembled packet let payload = buffer.freeze(); match Arc::try_unwrap(meta) { Ok(m) => { @@ -251,14 +243,12 @@ mod tests { use super::*; - /// Test fragment reassembly buffer #[test_log::test(tokio::test)] async fn test_fragment_reassembly_single_fragment() { let buffer = FragmentReassemblyBuffer::new(); let target = TargetAddr::IPv4(Ipv4Addr::new(127, 0, 0, 1), 8080); let payload = Bytes::from("test payload"); - // Single fragment packet let result = buffer .add_fragment( FragmentInfo { @@ -278,7 +268,6 @@ mod tests { assert_eq!(packet.payload, payload); } - /// Test fragment reassembly with multiple fragments #[test_log::test(tokio::test)] async fn test_fragment_reassembly_multiple_fragments() { let buffer = FragmentReassemblyBuffer::new(); @@ -287,7 +276,6 @@ mod tests { let frag1 = Bytes::from("Hello "); let frag2 = Bytes::from("World"); - // Add first fragment let result1 = buffer .add_fragment( FragmentInfo { @@ -303,7 +291,6 @@ mod tests { .await; assert!(result1.is_none(), "First fragment should not complete packet"); - // Add second fragment - should complete let result2 = buffer .add_fragment( FragmentInfo { @@ -323,7 +310,6 @@ mod tests { assert_eq!(packet.payload, Bytes::from("Hello World")); } - /// Test fragment reassembly with out-of-order fragments #[test_log::test(tokio::test)] async fn test_fragment_reassembly_out_of_order() { let buffer = FragmentReassemblyBuffer::new(); @@ -333,7 +319,6 @@ mod tests { let frag1 = Bytes::from("B"); let frag2 = Bytes::from("C"); - // Add fragments out of order: 2, 0, 1 assert!( buffer .add_fragment( @@ -386,13 +371,11 @@ mod tests { assert_eq!(packet.payload, Bytes::from("ABC")); } - /// Test multiple simultaneous fragmentations #[test_log::test(tokio::test)] async fn test_multiple_simultaneous_fragmentations() { let buffer = FragmentReassemblyBuffer::new(); let target = TargetAddr::IPv4(Ipv4Addr::new(127, 0, 0, 1), 8080); - // Start two different packets buffer .add_fragment( FragmentInfo { @@ -420,7 +403,6 @@ mod tests { ) .await; - // Complete first packet let result1 = buffer .add_fragment( FragmentInfo { @@ -437,7 +419,6 @@ mod tests { assert!(result1.is_some()); assert_eq!(result1.unwrap().payload, Bytes::from("A1A2")); - // Complete second packet let result2 = buffer .add_fragment( FragmentInfo { @@ -455,13 +436,11 @@ mod tests { assert_eq!(result2.unwrap().payload, Bytes::from("B1B2")); } - /// Test fragment cleanup (expired fragments) #[test_log::test(tokio::test)] async fn test_fragment_cleanup() { let buffer = FragmentReassemblyBuffer::new(); let target = TargetAddr::IPv4(Ipv4Addr::new(127, 0, 0, 1), 8080); - // Add incomplete fragment buffer .add_fragment( FragmentInfo { @@ -476,21 +455,15 @@ mod tests { ) .await; - // Wait for pending tasks to ensure the fragment is properly stored buffer.fragments.run_pending_tasks().await; assert_eq!(buffer.fragments.entry_count(), 1, "Should have one incomplete packet"); - // Manually remove the entry to simulate cleanup buffer.fragments.remove(&(1, 400)).await; buffer.fragments.run_pending_tasks().await; assert_eq!(buffer.fragments.entry_count(), 0, "Fragments should be cleaned up"); } - // ---------------------------------------------------------------------- - // PR2 regression tests - // ---------------------------------------------------------------------- - /// `frag_total == 0` and `frag_id >= frag_total` are both forbidden by /// the spec, but are attacker-controlled on the wire. The buffer must /// drop such fragments instead of producing a zero-byte "reassembled" diff --git a/crates/tuic-server/src/acl.rs b/crates/tuic-server/src/acl.rs index 79624bc..d7aa7e3 100644 --- a/crates/tuic-server/src/acl.rs +++ b/crates/tuic-server/src/acl.rs @@ -130,10 +130,6 @@ pub enum AclPortSpec { Range(u16, u16), } -// ============================================================================ -// Matching Logic -// ============================================================================ - #[cfg(test)] impl AclRule { /// Returns `true` if the supplied socket address, port and transport @@ -217,10 +213,6 @@ impl AclPortEntry { } } -// ============================================================================ -// Parsing Functions -// ============================================================================ - /// Parse a single ACL rule from string format pub(crate) fn parse_acl_rule(rule: &str) -> eyre::Result { if rule.starts_with('#') || rule.is_empty() { @@ -378,10 +370,6 @@ fn parse_multiline_acl_string(input: &str) -> eyre::Result> { .collect() } -// ============================================================================ -// Serde Deserialize Implementations -// ============================================================================ - impl<'de> Deserialize<'de> for AclAddress { fn deserialize(deserializer: D) -> Result where @@ -550,10 +538,6 @@ where deserializer.deserialize_any(AclVisitor) } -// ============================================================================ -// ACL → Metacubex Rule conversion -// ============================================================================ - /// Convert a list of legacy ACL rules into Metacubex-style [`wrule::Rule`]s. /// /// Each [`AclRule`] may expand to *multiple* Metacubex rules because: @@ -571,13 +555,10 @@ pub fn acl_to_rules(acl: &[AclRule]) -> Vec { fn acl_rule_to_rules(acl: &AclRule) -> Vec { let target = normalize_outbound(&acl.outbound); - // --- Build address-level condition(s) --- let addr_rules = address_to_rule_types(&acl.addr); - // --- Build port/protocol condition(s) --- let port_conds = ports_to_conditions(&acl.ports); - // --- Combine --- // When there are no port conditions, one rule per address condition. // When there are port conditions, AND(addr, port) for each combination. if port_conds.is_empty() { @@ -786,10 +767,6 @@ mod tests { SocketAddr::new(IpAddr::V6(addr.parse::().unwrap()), port) } - // ======================================================================== - // Address Matching Tests - // ======================================================================== - #[tokio::test] async fn ip_exact_match() { let rule = AclRule { @@ -1039,10 +1016,6 @@ mod tests { assert!(!rule.matching(v6("2001:db8::1", 80), 80, true).await); } - // ======================================================================== - // Port Matching Tests - // ======================================================================== - #[tokio::test] async fn ports_none_matches_everything() { let rule = AclRule { @@ -1266,10 +1239,6 @@ mod tests { assert!(!rule.matching(v4("1.2.3.4", 5101), 5101, false).await); } - // ======================================================================== - // Parsing Tests - // ======================================================================== - #[tokio::test] async fn parse_simple_rule() -> eyre::Result<()> { let rule_str = "allow 192.168.1.0/24 tcp/443,udp/53"; @@ -1418,10 +1387,6 @@ block 10.0.0.0/8 udp/53 Ok(()) } - // ======================================================================== - // Display Tests - // ======================================================================== - #[tokio::test] async fn display_acl_rule() { let rule = AclRule { @@ -1501,10 +1466,6 @@ block 10.0.0.0/8 udp/53 assert_eq!(ports.to_string(), "tcp/80,udp/53"); } - // ======================================================================== - // Deserialization Tests - // ======================================================================== - #[tokio::test] async fn deserialize_address_ip() -> eyre::Result<()> { let toml = r#"addr = "192.168.1.1""#; @@ -1711,10 +1672,6 @@ addr = "private" Ok(()) } - // ======================================================================== - // ACL → Metacubex Conversion Tests - // ======================================================================== - #[test] fn convert_ip_acl_to_rule() { let acl = AclRule { @@ -2127,7 +2084,7 @@ addr = "private" /// tried first; `ipnet` accepts `/32` for IPv6 too and silently returned /// the network `2001:db8::/32`, expanding the ACL by ~96 bits. #[test] - fn pr4_acl_ipv6_address_yields_128_host_route() { + fn acl_ipv6_address_yields_128_host_route() { let acl = AclRule { outbound: "direct".into(), addr: AclAddress::Ip("2001:db8::1".into()), @@ -2154,7 +2111,7 @@ addr = "private" /// IPv4 case stays correct. #[test] - fn pr4_acl_ipv4_address_yields_32_host_route() { + fn acl_ipv4_address_yields_32_host_route() { let acl = AclRule { outbound: "direct".into(), addr: AclAddress::Ip("10.0.0.5".into()), @@ -2178,7 +2135,7 @@ addr = "private" /// of falling back to `0.0.0.0/32`, which previously turned bad data into /// a silently "matches nothing" rule that hid the configuration error. #[test] - fn pr4_acl_malformed_ip_drops_rule() { + fn acl_malformed_ip_drops_rule() { let acl = AclRule { outbound: "direct".into(), addr: AclAddress::Ip("not-an-ip".into()), @@ -2195,14 +2152,14 @@ addr = "private" // ------------------------------------------------------------------ #[test] - fn pr5_format_protocol_zero_alloc_output() { + fn format_protocol_zero_alloc_output() { assert_eq!(super::format_protocol(&Some(super::AclProtocol::Tcp)), "tcp/"); assert_eq!(super::format_protocol(&Some(super::AclProtocol::Udp)), "udp/"); assert_eq!(super::format_protocol(&None), ""); } #[test] - fn pr5_format_optional_parts_display() { + fn format_optional_parts_display() { // Empty case — no ports + no hijack ⇒ empty Display. let s = super::format_optional_parts(&None, &None).to_string(); assert_eq!(s, ""); diff --git a/crates/tuic-server/src/config.rs b/crates/tuic-server/src/config.rs index 4118345..7150dae 100644 --- a/crates/tuic-server/src/config.rs +++ b/crates/tuic-server/src/config.rs @@ -422,10 +422,6 @@ pub struct ExperimentalConfig { pub drop_private: bool, } -// ============================================================================ -// Serde helpers for wind_core::rule::Rule -// ============================================================================ - /// Serialize `Vec` as an array of strings. fn serialize_rules(rules: &[Rule], serializer: S) -> Result where @@ -510,7 +506,6 @@ fn generate_random_alphanumeric_string(min: usize, max: usize) -> String { impl Config { pub fn migrate(&mut self) { - // Migrate TLS-related fields #[allow(deprecated)] { if let Some(self_sign) = self.__self_sign { @@ -585,7 +580,6 @@ impl Config { } users }, - // Provide a minimal outbound example outbound: OutboundConfig { default: OutboundRule { kind: "direct".into(), @@ -594,7 +588,6 @@ impl Config { }, ..Default::default() }, - // Example ACL list (empty by default) acl: Vec::new(), ..Default::default() } @@ -669,7 +662,6 @@ impl From for LevelFilter { fn infer_config_format(content: &str) -> ConfigFormat { let trimmed = content.trim_start(); - // Check for JSON/JSON5 format if trimmed.starts_with('{') || trimmed.starts_with('[') { return ConfigFormat::Json; } @@ -680,7 +672,6 @@ fn infer_config_format(content: &str) -> ConfigFormat { return ConfigFormat::Yaml; } - // Try to detect YAML-style indentation patterns let lines: Vec<&str> = content .lines() .filter(|l| !l.trim().is_empty() && !l.trim_start().starts_with('#')) @@ -721,7 +712,6 @@ fn infer_config_format(content: &str) -> ConfigFormat { while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_' || bytes[i] == b'-') { i += 1; } - // Skip whitespace then expect `=` while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') { i += 1; } @@ -732,7 +722,6 @@ fn infer_config_format(content: &str) -> ConfigFormat { (trimmed.starts_with('[') && trimmed.contains(']') && !trimmed.contains(':')) || is_toml_assignment(trimmed) }); - // Decide based on patterns found if has_toml_patterns && !has_yaml_patterns { ConfigFormat::Toml } else if has_yaml_patterns && !has_toml_patterns { @@ -742,7 +731,6 @@ fn infer_config_format(content: &str) -> ConfigFormat { // (YAML could have = in values, but TOML [sections] are more specific) ConfigFormat::Toml } else { - // Default to Unknown if we can't determine ConfigFormat::Unknown } } @@ -768,7 +756,6 @@ async fn find_config_in_dir(dir: &PathBuf) -> eyre::Result { let mut entries = tokio::fs::read_dir(dir).await?; let mut config_files = Vec::new(); - // Collect all files with recognizable config extensions while let Some(entry) = entries.next_entry().await? { let path = entry.path(); if path.is_file() { @@ -797,7 +784,6 @@ async fn find_config_in_dir(dir: &PathBuf) -> eyre::Result { } pub async fn parse_config(cli: Cli, env_state: EnvState) -> eyre::Result { - // Handle --init flag if cli.init { warn!("Generating an example configuration to config.toml......"); @@ -826,7 +812,6 @@ pub async fn parse_config(cli: Cli, env_state: EnvState) -> eyre::Result )); }; - // Check if config file exists if !cfg_path.exists() { return Err(eyre::eyre!("Config file not found: {}", cfg_path.display())); } @@ -882,7 +867,6 @@ pub async fn parse_config(cli: Cli, env_state: EnvState) -> eyre::Result ConfigFormat::Toml => figmet.merge(Toml::file(&cfg_path)), ConfigFormat::Yaml => figmet.merge(Yaml::file(&cfg_path)), ConfigFormat::Unknown => { - // Try to infer format from file content let content = tokio::fs::read_to_string(&cfg_path).await?; let inferred_format = infer_config_format(&content); @@ -902,7 +886,6 @@ pub async fn parse_config(cli: Cli, env_state: EnvState) -> eyre::Result let mut config: Config = figmet.extract()?; - // Migrate legacy fields to new nested structure config.migrate(); if config.data_dir.to_str() == Some("") { @@ -914,7 +897,6 @@ pub async fn parse_config(cli: Cli, env_state: EnvState) -> eyre::Result tokio::fs::create_dir_all(&config.data_dir).await?; }; - // Determine certificate and key paths let base_dir = config.data_dir.clone(); config.tls.certificate = if config.tls.auto_ssl && config.tls.certificate.to_str() == Some("") { config.data_dir.join(format!("{}.cer.pem", config.tls.hostname)) @@ -996,7 +978,6 @@ mod tests { assert_eq!(result.users.get(&uuid1), Some(&"password1".to_string())); assert_eq!(result.users.get(&uuid2), Some(&"password2".to_string())); - // Cleanup test directories let _ = tokio::fs::remove_dir_all("__test__custom_data").await; Ok(()) } @@ -1017,7 +998,7 @@ mod tests { assert_eq!(result.users.get(&uuid), Some(&"old_password".to_string())); assert!(!result.tls.self_sign); - assert!(result.data_dir.ends_with("__test__legacy_data")); // Cleanup test directories + assert!(result.data_dir.ends_with("__test__legacy_data")); let _ = tokio::fs::remove_dir_all("__test__legacy_data").await; } @@ -1040,7 +1021,6 @@ mod tests { current_dir.join("__test__relative_path").join("certs/server.key") ); - // Cleanup test directories let _ = tokio::fs::remove_dir_all("__test__relative_path").await; } @@ -1063,18 +1043,15 @@ mod tests { assert_eq!(result.tls.certificate, expected_cert); assert_eq!(result.tls.private_key, expected_key); - // Cleanup test directories let _ = tokio::fs::remove_dir_all("__test__ssl_data").await; } #[tokio::test] async fn test_error_handling() { - // Test Invalid TOML let config = "invalid toml content"; let result = test_parse_config(config, ".toml").await; assert!(result.is_err()); - // Test Invalid JSON let config = "{ invalid json }"; let result = test_parse_config(config, ".json").await; assert!(result.is_err()); @@ -1309,13 +1286,12 @@ mod tests { #[tokio::test] async fn test_congestion_control_variants() { - // Test BBR let config_bbr = include_str!("../tests/config/congestion_control_bbr.toml"); let result = test_parse_config(config_bbr, ".toml").await.unwrap(); assert_eq!(result.backend.quinn.congestion_control.controller, CongestionController::Bbr); - // Test NewReno (note: lowercase 'newreno' is the valid variant) + // note: lowercase 'newreno' is the valid variant let config_new_reno = include_str!("../tests/config/congestion_control_newreno.toml"); let result = test_parse_config(config_new_reno, ".toml").await.unwrap(); @@ -1658,7 +1634,6 @@ send_window = 12345678 let temp_dir = tempdir().unwrap(); let dir_path = temp_dir.path(); - // Test with JSON let json_dir = dir_path.join("json_test"); fs::create_dir(&json_dir).unwrap(); fs::write(json_dir.join("config.json"), r#"{"log_level": "debug"}"#).unwrap(); @@ -1672,7 +1647,6 @@ send_window = 12345678 let result = parse_config(cli, EnvState::default()).await.unwrap(); assert_eq!(result.log_level, LogLevel::Debug); - // Test with YAML let yaml_dir = dir_path.join("yaml_test"); fs::create_dir(&yaml_dir).unwrap(); fs::write(yaml_dir.join("config.yaml"), "log_level: warn").unwrap(); @@ -1854,10 +1828,6 @@ send_window = 12345678 assert!(result.is_ok()); } - // ==================================================================== - // Metacubex-style rules tests - // ==================================================================== - #[tokio::test] async fn test_rules_parsing() { let config = include_str!("../tests/config/rules_parsing.toml"); @@ -1866,30 +1836,22 @@ send_window = 12345678 assert_eq!(result.rules.len(), 8); - // DOMAIN,ad.example.com,REJECT assert_eq!(result.rules[0].to_string(), "DOMAIN,ad.example.com,REJECT"); assert_eq!(result.rules[0].target, "REJECT"); - // DOMAIN-SUFFIX,google.com,proxy assert_eq!(result.rules[1].to_string(), "DOMAIN-SUFFIX,google.com,proxy"); - // DOMAIN-KEYWORD,track,reject assert_eq!(result.rules[2].target, "reject"); - // IP-CIDR,10.0.0.0/8,direct,no-resolve assert_eq!(result.rules[3].target, "direct"); assert!(result.rules[3].no_resolve()); - // IP-CIDR6,fc00::/7,direct assert_eq!(result.rules[4].to_string(), "IP-CIDR6,fc00::/7,direct"); - // DST-PORT,443,proxy assert_eq!(result.rules[5].to_string(), "DST-PORT,443,proxy"); - // NETWORK,udp,direct assert_eq!(result.rules[6].to_string(), "NETWORK,udp,direct"); - // MATCH,proxy assert_eq!(result.rules[7].to_string(), "MATCH,proxy"); } @@ -1985,13 +1947,13 @@ rules = ["INVALID_TYPE,value,target"] /// secret like `aGVsbG8=`) was misclassified as TOML because the heuristic /// reduced to `trimmed.contains('=')` due to `&&`/`||` precedence. #[test] - fn pr4_yaml_with_equals_in_value_is_yaml() { + fn yaml_with_equals_in_value_is_yaml() { let yaml = "secret: aGVsbG8=\nfoo: bar\n"; assert_eq!(infer_config_format(yaml), ConfigFormat::Yaml); } #[test] - fn pr4_toml_section_still_detected() { + fn toml_section_still_detected() { // `infer_config_format` short-circuits `starts_with('[')` to JSON, so // any TOML file starting with a `[section]` would be misidentified as // JSON regardless of the PR4-L heuristic fix. That JSON shortcut is a @@ -2004,14 +1966,14 @@ rules = ["INVALID_TYPE,value,target"] } #[test] - fn pr4_toml_bare_assignment_still_detected() { + fn toml_bare_assignment_still_detected() { // `key = "value"` without a section header is still valid TOML. let toml = "log_level = \"info\"\n"; assert_eq!(infer_config_format(toml), ConfigFormat::Toml); } #[test] - fn pr4_yaml_with_indented_block_not_misread_as_toml() { + fn yaml_with_indented_block_not_misread_as_toml() { // Indented list under a key — pure YAML, no top-level `=`. let yaml = "rules:\n - foo=bar\n - baz\n"; assert_eq!(infer_config_format(yaml), ConfigFormat::Yaml); diff --git a/crates/tuic-server/src/lib.rs b/crates/tuic-server/src/lib.rs index 8a59952..f582944 100644 --- a/crates/tuic-server/src/lib.rs +++ b/crates/tuic-server/src/lib.rs @@ -1,6 +1,3 @@ -// Library interface for tuic-server -// This allows the server to be used as a library in integration tests - use std::sync::Arc; use tokio_util::sync::CancellationToken; @@ -39,11 +36,9 @@ pub async fn run(cfg: Config) -> eyre::Result<()> { pub async fn run_with_cancel(cfg: Config, cancel: CancellationToken) -> eyre::Result<()> { let ctx = Arc::new(AppContext { cancel, cfg }); - // Create the inbound (quinn or quiche, per `backend.mode`) and adapter. let (inbound, adapter) = wind_adapter::create_inbound(ctx).await?; tracing::info!("Starting TUIC server"); - // Start the server inbound.listen(&adapter).await } diff --git a/crates/tuic-server/src/main.rs b/crates/tuic-server/src/main.rs index 05e79fd..cf1379f 100644 --- a/crates/tuic-server/src/main.rs +++ b/crates/tuic-server/src/main.rs @@ -26,7 +26,6 @@ async fn main() -> eyre::Result<()> { let cfg = match parse_config(cli, env_state).await { Ok(cfg) => cfg, Err(err) => { - // Check if it's a Control error (Help or Version) if let Some(control) = err.downcast_ref::() { println!("{}", control); process::exit(0); diff --git a/crates/tuic-server/src/utils.rs b/crates/tuic-server/src/utils.rs index ace6c8b..550a09b 100644 --- a/crates/tuic-server/src/utils.rs +++ b/crates/tuic-server/src/utils.rs @@ -31,7 +31,6 @@ mod tests { #[test] fn test_stack_prefer_serde() { - // Test serialization let v4_only = StackPrefer::V4only; let json = serde_json::to_string(&v4_only).unwrap(); assert_eq!(json, "\"v4only\""); @@ -40,7 +39,6 @@ mod tests { let json = serde_json::to_string(&v6_only).unwrap(); assert_eq!(json, "\"v6only\""); - // Test deserialization let v4_first: StackPrefer = serde_json::from_str("\"v4first\"").unwrap(); assert_eq!(v4_first, StackPrefer::V4first); @@ -50,7 +48,6 @@ mod tests { #[test] fn test_stack_prefer_variants() { - // Test all variants exist and are distinct let modes = [ StackPrefer::V4only, StackPrefer::V6only, @@ -58,7 +55,7 @@ mod tests { StackPrefer::V6first, ]; - assert_eq!(modes.len(), 4); // Test equality + assert_eq!(modes.len(), 4); assert_eq!(StackPrefer::V4only, StackPrefer::V4only); assert_ne!(StackPrefer::V4only, StackPrefer::V6only); } diff --git a/crates/tuic-tests/src/lib.rs b/crates/tuic-tests/src/lib.rs index f9ddcf4..511806d 100644 --- a/crates/tuic-tests/src/lib.rs +++ b/crates/tuic-tests/src/lib.rs @@ -129,7 +129,6 @@ pub async fn start_quiche_pair(server_port: u16, socks_port: u16, zero_rtt: bool format!("127.0.0.1:{socks_port}") } -// Helper function to create and run a TCP echo server pub async fn run_tcp_echo_server(bind_addr: &str, test_name: &str) -> (tokio::task::JoinHandle<()>, std::net::SocketAddr) { use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, @@ -182,7 +181,6 @@ pub async fn run_tcp_echo_server(bind_addr: &str, test_name: &str) -> (tokio::ta (echo_task, echo_addr) } -// Helper function to create and run a UDP echo server pub async fn run_udp_echo_server( bind_addr: &str, test_name: &str, @@ -226,7 +224,6 @@ pub async fn run_udp_echo_server( (echo_task, echo_addr, echo_server) } -// Helper function to test TCP connection through SOCKS5 pub async fn test_tcp_through_socks5( socks5_addr: &str, target_addr: std::net::SocketAddr, @@ -299,7 +296,6 @@ pub async fn test_tcp_through_socks5( } } -// Helper function to test UDP connection through SOCKS5 pub async fn test_udp_through_socks5( socks5_addr: &str, target_addr: std::net::SocketAddr, @@ -388,7 +384,6 @@ pub async fn test_udp_through_socks5( } } -// Helper function to create and run a SOCKS5 server // This server can be used as a proxy for testing TUIC client proxy // configuration pub async fn run_socks5_server( diff --git a/crates/tuic-tests/tests/integration_tests.rs b/crates/tuic-tests/tests/integration_tests.rs index 263f8c5..4d55e0d 100644 --- a/crates/tuic-tests/tests/integration_tests.rs +++ b/crates/tuic-tests/tests/integration_tests.rs @@ -1,6 +1,3 @@ -// Integration tests for TUIC protocol -// Tests the encode/decode round-trip for all protocol types - use std::{ net::{Ipv4Addr, Ipv6Addr}, time::Duration, @@ -18,21 +15,18 @@ use tuic_tests::{ use uuid::Uuid; use wind_tuic::proto::{Address, AddressCodec, CmdCodec, CmdType, Command, Header, HeaderCodec}; -// Helper function to encode and decode a header fn roundtrip_header(header: Header) -> Header { let mut buf = BytesMut::new(); HeaderCodec.encode(header, &mut buf).unwrap(); HeaderCodec.decode(&mut buf).unwrap().unwrap() } -// Helper function to encode and decode a command fn roundtrip_command(cmd_type: CmdType, command: Command) -> Command { let mut buf = BytesMut::new(); CmdCodec(cmd_type).encode(command, &mut buf).unwrap(); CmdCodec(cmd_type).decode(&mut buf).unwrap().unwrap() } -// Helper function to encode and decode an address fn roundtrip_address(addr: Address) -> Address { let mut buf = BytesMut::new(); AddressCodec.encode(addr, &mut buf).unwrap(); @@ -41,9 +35,6 @@ fn roundtrip_address(addr: Address) -> Address { #[test] fn test_full_protocol_roundtrip() { - // Test all header and command types can be encoded and decoded correctly - - // 1. Authenticate let uuid = Uuid::parse_str("123e4567-e89b-12d3-a456-426614174000").unwrap(); let token = [42u8; 32]; let cmd = Command::Auth { uuid, token }; @@ -63,7 +54,6 @@ fn test_full_protocol_roundtrip() { _ => panic!("Wrong command type"), } - // 2. Connect with different address types let addresses: Vec
= vec![ Address::None, Address::Domain("example.com".to_string(), 443), @@ -82,7 +72,6 @@ fn test_full_protocol_roundtrip() { assert_eq!(decoded_addr, addr); } - // 3. Packet let cmd = Command::Packet { assoc_id: 123, pkt_id: 456, @@ -116,7 +105,6 @@ fn test_full_protocol_roundtrip() { let decoded_addr = roundtrip_address(addr.clone()); assert_eq!(decoded_addr, addr); - // 4. Dissociate let cmd = Command::Dissociate { assoc_id: 999 }; let decoded_header = roundtrip_header(Header::new(CmdType::Dissociate)); @@ -130,7 +118,6 @@ fn test_full_protocol_roundtrip() { _ => panic!("Wrong command type"), } - // 5. Heartbeat let decoded_header = roundtrip_header(Header::new(CmdType::Heartbeat)); assert_eq!(decoded_header.command, CmdType::Heartbeat); @@ -173,7 +160,6 @@ fn test_fragmented_udp_packets() { #[test] fn test_edge_case_values() { - // Test edge case values for Packet command let test_cases: Vec<(u16, u16, u8, u8, u16)> = vec![ (0, 0, 1, 0, 0), // Minimum values (u16::MAX, u16::MAX, u8::MAX, u8::MAX - 1, u16::MAX), // Maximum values @@ -212,7 +198,6 @@ fn test_edge_case_values() { #[test] fn test_various_domain_names() { - // Test various domain name lengths and formats let binding = "a".repeat(63); let domains = vec![ "a.b", // Short domain @@ -253,7 +238,6 @@ async fn test_server_client_integration() -> eyre::Result<()> { #[cfg(feature = "ring")] let _ = rustls::crypto::ring::default_provider().install_default(); - // Create a minimal server configuration for testing // IMPORTANT: We need to configure ACL to allow localhost connections for // testing let server_config = tuic_server::Config { @@ -289,10 +273,8 @@ async fn test_server_client_integration() -> eyre::Result<()> { ..Default::default() }; - // Spawn server in background info!("[Integration Test] Starting TUIC server on 127.0.0.1:8443..."); let server_handle = tokio::spawn(async move { - // Run server with a timeout match timeout(Duration::from_secs(10), tuic_server::run(server_config)).await { Ok(Ok(())) => info!("[Integration Test] Server completed successfully"), Ok(Err(e)) => error!("[Integration Test] Server error: {}", e), @@ -305,7 +287,6 @@ async fn test_server_client_integration() -> eyre::Result<()> { tokio::time::sleep(Duration::from_secs(1)).await; info!("[Integration Test] Server should be ready now"); - // Create a client configuration that connects to the test server let client_config = tuic_client::Config { relay: tuic_client::config::Relay { server: ("127.0.0.1".to_string(), 8443), @@ -337,7 +318,6 @@ async fn test_server_client_integration() -> eyre::Result<()> { log_level: "debug".to_string(), }; - // Spawn client in background with timeout info!("[Integration Test] Starting TUIC client with SOCKS5 server on 127.0.0.1:1080..."); let client_handle = tokio::spawn(async move { match timeout(Duration::from_secs(10), tuic_client::run(client_config)).await { @@ -347,7 +327,6 @@ async fn test_server_client_integration() -> eyre::Result<()> { } }); - // Wait for client to establish connection and start SOCKS5 server info!("[Integration Test] Waiting for client to connect and start SOCKS5 server..."); tokio::time::sleep(Duration::from_secs(2)).await; info!("[Integration Test] SOCKS5 proxy should be ready now\n"); @@ -371,37 +350,25 @@ async fn test_server_client_integration() -> eyre::Result<()> { } } - // ============================================================================ - // Test 1: Create a local TCP echo server and test TCP relay through SOCKS5 - // ============================================================================ let tcp_test = async { info!("[TCP Test] Starting TCP relay test..."); - // Start a local TCP echo server let (echo_task, echo_addr) = run_tcp_echo_server("127.0.0.1:0", "TCP Test").await; - // Give server time to start tokio::time::sleep(Duration::from_millis(200)).await; - // Test TCP connection through SOCKS5 let test_data = b"Hello, TUIC!"; test_tcp_through_socks5("127.0.0.1:1080", echo_addr, test_data, "TCP Test").await; - // Wait a bit to see if echo server gets anything info!("[TCP Test] Waiting for echo server to finish..."); tokio::time::sleep(Duration::from_millis(500)).await; - // Clean up echo_task.abort(); info!("[TCP Test] TCP test completed\n"); }; - // Run the TCP test with a timeout let _ = timeout(Duration::from_secs(6), tcp_test).await; - // ============================================================================ - // Test 2: Create a local UDP echo server and test UDP relay through SOCKS5 - // ============================================================================ let udp_test = async { use std::net::{IpAddr, Ipv4Addr}; @@ -409,28 +376,20 @@ async fn test_server_client_integration() -> eyre::Result<()> { info!("[UDP Test] Starting UDP relay test..."); info!("[UDP Test] ========================================\n"); - // Start a local UDP echo server let (echo_task, echo_addr, _echo_server) = run_udp_echo_server("127.0.0.1:0", "UDP Test").await; - // Give server time to start tokio::time::sleep(Duration::from_millis(100)).await; - // Test UDP connection through SOCKS5 let test_data = b"Hello, UDP through TUIC!"; let client_bind_addr = std::net::SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); test_udp_through_socks5("127.0.0.1:1080", echo_addr, test_data, "UDP Test", client_bind_addr).await; - // Clean up echo_task.abort(); info!("[UDP Test] UDP test completed\n"); }; - // Run the UDP test with a timeout let _ = timeout(Duration::from_secs(3), udp_test).await; - // ============================================================================ - // Test 3: Test multiple concurrent TCP connections - // ============================================================================ let concurrent_test = async { use fast_socks5::client::{Config, Socks5Stream}; use tokio::{ @@ -440,12 +399,10 @@ async fn test_server_client_integration() -> eyre::Result<()> { info!("[Concurrent Test] Starting concurrent TCP connections test..."); - // Start a local TCP server let server = TcpListener::bind("127.0.0.1:0").await.unwrap(); let server_addr = server.local_addr().unwrap(); info!("[Concurrent Test] TCP server started at: {}", server_addr); - // Spawn server task that handles multiple connections let server_task = tokio::spawn(async move { for i in 0..3 { if let Ok((mut socket, addr)) = server.accept().await { @@ -465,7 +422,6 @@ async fn test_server_client_integration() -> eyre::Result<()> { tokio::time::sleep(Duration::from_millis(100)).await; - // Create multiple concurrent connections through SOCKS5 info!("[Concurrent Test] Creating 3 concurrent connections through SOCKS5..."); let mut handles = vec![]; for i in 0..3 { @@ -511,7 +467,6 @@ async fn test_server_client_integration() -> eyre::Result<()> { handles.push(handle); } - // Wait for all connections to complete for (i, handle) in handles.into_iter().enumerate() { if let Err(e) = handle.await { error!("[Concurrent Test] Connection {} task failed: {}", i, e); @@ -523,10 +478,8 @@ async fn test_server_client_integration() -> eyre::Result<()> { info!("[Concurrent Test] Concurrent test completed\n"); }; - // Run the concurrent test with a timeout let _ = timeout(Duration::from_secs(5), concurrent_test).await; - // Clean up client_handle.abort(); server_handle.abort(); @@ -561,7 +514,6 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { info!("[IPv6 Test] Starting IPv6 Integration Test"); info!("[IPv6 Test] ========================================\n"); - // Create server configuration using IPv6 localhost [::1] let server_config = tuic_server::Config { log_level: tuic_server::config::LogLevel::Debug, server: "[::1]:8444".parse::()?, @@ -605,7 +557,6 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { ..Default::default() }; - // Spawn IPv6 server info!("[IPv6 Test] Starting TUIC server on [::1]:8444..."); let server_handle = tokio::spawn(async move { match timeout(Duration::from_secs(10), tuic_server::run(server_config)).await { @@ -615,12 +566,10 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { } }); - // Wait for server to start info!("[IPv6 Test] Waiting for server to initialize..."); tokio::time::sleep(Duration::from_secs(1)).await; info!("[IPv6 Test] Server should be ready now"); - // Create client configuration connecting to IPv6 server let client_config = tuic_client::Config { relay: tuic_client::config::Relay { server: ("[::1]".to_string(), 8444), @@ -661,7 +610,6 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { log_level: "debug".to_string(), }; - // Spawn client with IPv6 SOCKS5 server info!("[IPv6 Test] Starting TUIC client with SOCKS5 server on [::1]:1081..."); let client_handle = tokio::spawn(async move { match timeout(Duration::from_secs(10), tuic_client::run(client_config)).await { @@ -671,12 +619,10 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { } }); - // Wait for client to connect info!("[IPv6 Test] Waiting for client to connect and start SOCKS5 server..."); tokio::time::sleep(Duration::from_secs(2)).await; info!("[IPv6 Test] SOCKS5 proxy should be ready now\n"); - // Test SOCKS5 proxy connectivity on IPv6 use tokio::net::TcpStream; info!("[IPv6 Test] Testing SOCKS5 proxy connectivity on IPv6..."); match TcpStream::connect("[::1]:1081").await { @@ -691,18 +637,13 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { } } - // ============================================================================ - // Test 1: IPv6 TCP relay through SOCKS5 - // ============================================================================ let tcp_test = async { info!("[IPv6 TCP Test] Starting TCP relay test on IPv6..."); - // Start a local TCP echo server on IPv6 let (echo_task, echo_addr) = run_tcp_echo_server("[::1]:0", "IPv6 TCP Test").await; tokio::time::sleep(Duration::from_millis(200)).await; - // Test TCP connection through SOCKS5 on IPv6 let test_data = b"Hello IPv6 TUIC!"; test_tcp_through_socks5("[::1]:1081", echo_addr, test_data, "IPv6 TCP Test").await; @@ -712,20 +653,15 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { let _ = timeout(Duration::from_secs(6), tcp_test).await; - // ============================================================================ - // Test 2: IPv6 UDP relay through SOCKS5 - // ============================================================================ let udp_test = async { use std::net::{IpAddr, Ipv6Addr}; info!("[IPv6 UDP Test] Starting UDP relay test on IPv6..."); - // Start a local UDP echo server on IPv6 let (echo_task, echo_addr, _echo_server) = run_udp_echo_server("[::1]:0", "IPv6 UDP Test").await; tokio::time::sleep(Duration::from_millis(100)).await; - // Test UDP connection through SOCKS5 on IPv6 let test_data = b"Hello, IPv6 UDP through TUIC!"; let client_bind_addr = std::net::SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0); test_udp_through_socks5("[::1]:1081", echo_addr, test_data, "IPv6 UDP Test", client_bind_addr).await; @@ -736,7 +672,6 @@ async fn test_ipv6_server_client_integration() -> eyre::Result<()> { let _ = timeout(Duration::from_secs(3), udp_test).await; - // Clean up client_handle.abort(); server_handle.abort(); @@ -771,7 +706,6 @@ async fn test_client_proxy_configuration() -> eyre::Result<()> { info!("[Proxy Config Test] Starting Proxy Configuration Test"); info!("[Proxy Config Test] ========================================\n"); - // Create a minimal server for testing let server_config = tuic_server::Config { log_level: tuic_server::config::LogLevel::Debug, server: "127.0.0.1:8445".parse::()?, @@ -816,17 +750,14 @@ async fn test_client_proxy_configuration() -> eyre::Result<()> { tokio::time::sleep(Duration::from_millis(500)).await; - // Test: Client config with proxy settings info!("[Proxy Config Test] Test 1: Client with SOCKS5 proxy configuration"); - // Start a real SOCKS5 proxy server for testing let (socks5_handle, socks5_addr) = run_socks5_server("127.0.0.1:0", "Proxy Test 1", Some("proxy_user"), Some("proxy_pass")).await; info!("[Proxy Config Test] SOCKS5 proxy started at: {}", socks5_addr); tokio::time::sleep(Duration::from_millis(200)).await; - // Build config directly let config = tuic_client::config::Config { relay: tuic_client::config::Relay { server: ("127.0.0.1".to_string(), 8445), @@ -872,7 +803,6 @@ async fn test_client_proxy_configuration() -> eyre::Result<()> { let (echo_handle, echo_addr) = run_tcp_echo_server("127.0.0.1:0", "Proxy Test 1 Echo").await; tokio::time::sleep(Duration::from_millis(200)).await; - // Try to connect to echo server through SOCKS5 proxy info!("[Proxy Config Test] Testing connection through SOCKS5 proxy to echo server..."); let test_data = b"Hello through SOCKS5 proxy!"; let success = test_tcp_through_socks5(local_socks, echo_addr, test_data, "Proxy Test 1").await; @@ -883,7 +813,6 @@ async fn test_client_proxy_configuration() -> eyre::Result<()> { info!("[Proxy Config Test] ⚠ Could not verify SOCKS5 proxy connectivity (may be expected)"); } - // Clean up echo_handle.abort(); client_handle.abort(); socks5_handle.abort(); diff --git a/crates/tuic-tests/tests/quiche_integration.rs b/crates/tuic-tests/tests/quiche_integration.rs index 4a915d7..ead5689 100644 --- a/crates/tuic-tests/tests/quiche_integration.rs +++ b/crates/tuic-tests/tests/quiche_integration.rs @@ -32,7 +32,6 @@ use tuic_tests::{ async fn quiche_tcp_and_udp_relay() -> eyre::Result<()> { let socks = start_quiche_pair(8460, 1090, false).await; - // --- TCP relay --- let (tcp_echo, tcp_addr) = run_tcp_echo_server("127.0.0.1:0", "Quiche TCP").await; tokio::time::sleep(Duration::from_millis(200)).await; let tcp_ok = timeout( diff --git a/crates/wind-core/src/dispatcher.rs b/crates/wind-core/src/dispatcher.rs index 0ac18a2..9a42fe9 100644 --- a/crates/wind-core/src/dispatcher.rs +++ b/crates/wind-core/src/dispatcher.rs @@ -28,10 +28,6 @@ use crate::{ udp::UdpStream, }; -// ============================================================================ -// Public types -// ============================================================================ - /// Boxed future alias used throughout this module. /// /// Both `Send` and `Sync` are required so the future satisfies the @@ -51,10 +47,6 @@ pub enum RouteAction { Forward(String), } -// ============================================================================ -// Router trait -// ============================================================================ - /// Determines which outbound handler should serve a connection. /// /// Implementations are free to perform DNS resolution, consult ACL tables, or @@ -67,10 +59,6 @@ pub trait Router: Send + Sync + 'static { fn route(&self, target: &TargetAddr, is_tcp: bool) -> impl Future> + Send; } -// ============================================================================ -// OutboundAction trait -// ============================================================================ - /// Object-safe outbound handler. /// /// Each concrete outbound strategy (direct connect, SOCKS5 proxy, …) @@ -89,10 +77,6 @@ pub trait OutboundAction: Send + Sync + 'static { async fn handle_udp(&self, stream: UdpStream) -> eyre::Result<()>; } -// ============================================================================ -// Dispatcher -// ============================================================================ - /// Routes inbound connections to named outbound handlers. /// /// # Construction @@ -143,10 +127,6 @@ impl Clone for Dispatcher { } } -// ============================================================================ -// InboundCallback implementation -// ============================================================================ - impl InboundCallback for Dispatcher { async fn handle_tcpstream(&self, target_addr: TargetAddr, stream: impl AbstractTcpStream + 'static) -> eyre::Result<()> { let span = tracing::debug_span!("dispatch_tcp", target = %target_addr); @@ -237,10 +217,6 @@ impl Dispatcher { } } -// ============================================================================ -// AclRouter – built-in Router backed by Rule list -// ============================================================================ - /// A built-in [`Router`] that evaluates a list of [`Rule`]s in order. /// /// The first matching rule determines the outbound. If no rule matches, the @@ -338,10 +314,6 @@ fn rule_target_to_action(target: &str, rule: &Rule) -> RouteAction { } } -// ============================================================================ -// Adapter: AbstractOutbound → OutboundAction -// ============================================================================ - use crate::AbstractOutbound; /// Placeholder type used for the `via` parameter when no outbound chaining @@ -394,10 +366,6 @@ impl OutboundAction for OutboundAsA } } -// ============================================================================ -// Tests -// ============================================================================ - #[cfg(test)] mod tests { use std::sync::{ @@ -407,8 +375,6 @@ mod tests { use super::*; - // -- Helpers -- - fn parse_rules(text: &str) -> Vec { Rule::parse_rules(text).into_iter().filter_map(Result::ok).collect() } @@ -441,8 +407,6 @@ mod tests { } } - // -- AclRouter unit tests -- - #[tokio::test] async fn acl_router_domain_suffix_match() { let router = AclRouter::new(parse_rules("DOMAIN-SUFFIX,google.com,proxy"), "direct"); @@ -576,8 +540,6 @@ mod tests { assert!(matches!(action, RouteAction::Forward(name) if name == "default")); } - // -- Dispatcher + AclRouter integration tests -- - #[tokio::test] async fn dispatcher_routes_tcp_to_correct_handler() { let rules = parse_rules( @@ -594,7 +556,6 @@ mod tests { dispatcher.add_handler("proxy_out", proxy_handler.clone()); dispatcher.add_handler("default", default_handler.clone()); - // Create a duplex stream let (client, _server) = tokio::io::duplex(1024); let target = TargetAddr::Domain("app.proxy.me".into(), 443); @@ -692,8 +653,6 @@ mod tests { assert!(result.is_err()); } - // -- Port range routing -- - #[tokio::test] async fn acl_router_dst_port_range() { let router = AclRouter::new(parse_rules("DST-PORT,8000-9000,proxy"), "direct"); @@ -707,8 +666,6 @@ mod tests { assert!(matches!(action, RouteAction::Forward(name) if name == "direct")); } - // -- Compound rules in routing -- - #[tokio::test] async fn acl_router_and_compound() { let rules = parse_rules("AND,((DOMAIN-SUFFIX,example.com),(DST-PORT,443)),secure_proxy"); @@ -764,8 +721,6 @@ mod tests { assert!(matches!(action, RouteAction::Forward(name) if name == "direct")); } - // -- SrcIpCidr in routing (no src_ip in TargetAddr context → never matches) -- - #[tokio::test] async fn acl_router_src_ip_cidr_no_match_without_context() { let router = AclRouter::new(parse_rules("SRC-IP-CIDR,192.168.0.0/16,local"), "default"); @@ -776,8 +731,6 @@ mod tests { assert!(matches!(action, RouteAction::Forward(name) if name == "default")); } - // -- Domain + port combination -- - #[tokio::test] async fn acl_router_domain_and_port_combination() { let rules = parse_rules( @@ -805,16 +758,12 @@ mod tests { assert!(matches!(action, RouteAction::Forward(name) if name == "direct")); } - // ------------------------------------------------------------------ - // PR4-A regression tests for `rule_target_to_action` - // ------------------------------------------------------------------ - /// Outbound names registered with mixed case must survive routing — /// previously the `name => RouteAction::Forward(name.to_string())` arm /// bound to the lowercased string and silently routed `Proxy_Out` to /// `proxy_out`, which `Dispatcher::resolve_handler` failed to find. #[tokio::test] - async fn pr4_router_forwards_with_original_case() { + async fn router_forwards_with_original_case() { let router = AclRouter::new(parse_rules("DOMAIN-SUFFIX,example.com,Proxy_Out"), "default"); let target = TargetAddr::Domain("foo.example.com".into(), 80); let action = router.route(&target, true).await.unwrap(); @@ -824,7 +773,7 @@ mod tests { /// Reject keywords are still recognised case-insensitively across all /// three spellings. #[tokio::test] - async fn pr4_router_reject_keywords_case_insensitive() { + async fn router_reject_keywords_case_insensitive() { for kw in ["REJECT", "Reject", "reject", "BLOCK", "Block", "deny", "Deny", "DENY"] { let r = AclRouter::new(parse_rules(&format!("DOMAIN-SUFFIX,blocked.com,{kw}")), "default"); let target = TargetAddr::Domain("a.blocked.com".into(), 443); diff --git a/crates/wind-core/src/rule.rs b/crates/wind-core/src/rule.rs index c2bec39..bbc0fb8 100644 --- a/crates/wind-core/src/rule.rs +++ b/crates/wind-core/src/rule.rs @@ -86,10 +86,6 @@ use std::{fmt, net::IpAddr}; use ipnet::{IpNet, Ipv6Net}; use regex::Regex; -// ============================================================================ -// Core types -// ============================================================================ - /// Network protocol type. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NetworkType { @@ -132,7 +128,6 @@ pub struct DomainWildcardPattern { /// All supported rule types. pub enum RuleType { - // -- Domain rules -- /// Exact domain match (case-insensitive). Domain(String), /// Domain suffix match — also matches subdomains. @@ -150,7 +145,6 @@ pub enum RuleType { /// GeoSite database match — requires external lookup. GeoSite(String), - // -- IP rules (destination) -- /// IPv4/IPv6 CIDR match on destination IP. IpCidr(IpNet), /// IPv6-only CIDR match on destination IP. @@ -162,7 +156,6 @@ pub enum RuleType { /// GeoIP country code match on destination IP. GeoIp(String), - // -- IP rules (source) -- /// GeoIP match on source IP. SrcGeoIp(String), /// ASN match on source IP. @@ -172,7 +165,6 @@ pub enum RuleType { /// IP network suffix match on source IP. SrcIpSuffix(IpNet), - // -- Port rules -- /// Destination port match. DstPort(u16), /// Destination port range match (inclusive). @@ -182,7 +174,6 @@ pub enum RuleType { /// Source port range match (inclusive). SrcPortRange(u16, u16), - // -- Inbound rules -- /// Inbound listening port. InPort(u16), /// Inbound type (SOCKS / HTTP). @@ -192,7 +183,6 @@ pub enum RuleType { /// Inbound name identifier. InName(String), - // -- Process rules -- /// Exact process executable path. ProcessPath(String), /// Process path matched by regex. @@ -204,13 +194,11 @@ pub enum RuleType { /// Unix UID. Uid(u32), - // -- Network rules -- /// Network protocol match (TCP / UDP). Network(NetworkType), /// DSCP value match. Dscp(u8), - // -- Advanced rules -- /// External rule set reference (placeholder — always `false`). RuleSet(String), /// Logical AND of sub-rules. @@ -231,15 +219,10 @@ pub enum RuleType { /// more than one. SubRule(Vec, String), - // -- Catch-all -- /// Matches every connection. Match, } -// ============================================================================ -// MatchContext -// ============================================================================ - pub type GeoIpLookup<'a> = &'a dyn Fn(&str, IpAddr) -> bool; pub type AsnLookup<'a> = &'a dyn Fn(u32, IpAddr) -> bool; pub type GeoSiteLookup<'a> = &'a dyn Fn(&str, &str) -> bool; @@ -250,24 +233,20 @@ pub type GeoSiteLookup<'a> = &'a dyn Fn(&str, &str) -> bool; /// default). #[derive(Default)] pub struct MatchContext<'a> { - // -- Connection info -- pub src_ip: Option, pub dst_ip: Option, pub src_port: Option, pub dst_port: Option, pub domain: Option<&'a str>, - // -- Network info -- pub network: Option, pub dscp: Option, - // -- Inbound info -- pub inbound_port: Option, pub inbound_type: Option, pub inbound_user: Option<&'a str>, pub inbound_name: Option<&'a str>, - // -- Process info -- pub process_path: Option<&'a str>, pub process_name: Option<&'a str>, pub uid: Option, @@ -279,7 +258,6 @@ pub struct MatchContext<'a> { } // Manual impls because the function-pointer fields prevent derive. - impl<'a> Clone for MatchContext<'a> { fn clone(&self) -> Self { Self { @@ -328,15 +306,10 @@ impl fmt::Debug for MatchContext<'_> { } } -// ============================================================================ -// Matching -// ============================================================================ - impl Rule { /// Returns `true` if this rule matches the given context. pub fn matches(&self, ctx: &MatchContext) -> bool { match &self.rule_type { - // -- Domain -- RuleType::Domain(d) => ctx.domain.is_some_and(|h| h.eq_ignore_ascii_case(d)), // `suffix` and `kw` are already lowercased at parse time @@ -363,7 +336,6 @@ impl Rule { .and_then(|d| ctx.geosite_lookup.map(|f| f(site, d))) .unwrap_or(false), - // -- IP (destination) -- RuleType::IpCidr(net) => ctx.dst_ip.is_some_and(|ip| net.contains(&ip)), RuleType::IpCidr6(net) => ctx.dst_ip.is_some_and(|ip| match ip { @@ -380,7 +352,6 @@ impl Rule { .and_then(|ip| ctx.geoip_lookup.map(|f| f(country, ip))) .unwrap_or(false), - // -- IP (source) -- RuleType::SrcGeoIp(country) => ctx .src_ip .and_then(|ip| ctx.geoip_lookup.map(|f| f(country, ip))) @@ -392,13 +363,11 @@ impl Rule { RuleType::SrcIpSuffix(net) => ctx.src_ip.is_some_and(|ip| net.contains(&ip)), - // -- Port -- RuleType::DstPort(p) => ctx.dst_port.is_some_and(|dp| dp == *p), RuleType::DstPortRange(lo, hi) => ctx.dst_port.is_some_and(|dp| dp >= *lo && dp <= *hi), RuleType::SrcPort(p) => ctx.src_port.is_some_and(|sp| sp == *p), RuleType::SrcPortRange(lo, hi) => ctx.src_port.is_some_and(|sp| sp >= *lo && sp <= *hi), - // -- Inbound -- RuleType::InPort(p) => ctx.inbound_port.is_some_and(|ip| ip == *p), RuleType::InType(in_type) => ctx.inbound_type.is_some_and(|t| match in_type { @@ -409,18 +378,15 @@ impl Rule { RuleType::InUser(user) => ctx.inbound_user.is_some_and(|u| u == user), RuleType::InName(name) => ctx.inbound_name.is_some_and(|n| n == name), - // -- Process -- RuleType::ProcessPath(path) => ctx.process_path.is_some_and(|p| p == path), RuleType::ProcessPathRegex(re) => ctx.process_path.is_some_and(|p| re.is_match(p)), RuleType::ProcessName(name) => ctx.process_name.is_some_and(|n| n == name), RuleType::ProcessNameRegex(re) => ctx.process_name.is_some_and(|n| re.is_match(n)), RuleType::Uid(uid) => ctx.uid.is_some_and(|u| u == *uid), - // -- Network -- RuleType::Network(n) => ctx.network.is_some_and(|nn| nn == *n), RuleType::Dscp(d) => ctx.dscp.is_some_and(|dd| dd == *d), - // -- Advanced -- RuleType::RuleSet(_) => false, // placeholder RuleType::And(rules) => rules.iter().all(|r| r.matches(ctx)), RuleType::Or(rules) => rules.iter().any(|r| r.matches(ctx)), @@ -429,7 +395,6 @@ impl Rule { // "sub-rule reference by name" lookup is implemented. RuleType::SubRule(rules, _) => rules.iter().all(|r| r.matches(ctx)), - // -- Catch-all -- RuleType::Match => true, } } @@ -440,10 +405,6 @@ impl Rule { } } -// ============================================================================ -// Parsing -// ============================================================================ - /// Errors that can occur while parsing a rule string. #[derive(Debug, Clone, PartialEq)] pub enum RuleParseError { @@ -522,7 +483,6 @@ impl Rule { fn parse_type(type_str: &str, value: &str) -> Result { match type_str.to_ascii_uppercase().as_str() { - // Domain rules "DOMAIN" => Ok(RuleType::Domain(value.to_string())), // DOMAIN-SUFFIX / DOMAIN-KEYWORD values are stored in lower-case // ASCII at parse time, so the per-match hot path can compare @@ -544,7 +504,6 @@ impl Rule { } "GEOSITE" => Ok(RuleType::GeoSite(value.to_string())), - // IP rules (destination) "IP-CIDR" => { let net = value .parse::() @@ -580,7 +539,6 @@ impl Rule { } "GEOIP" => Ok(RuleType::GeoIp(value.to_string())), - // Source IP rules "SRC-GEOIP" => Ok(RuleType::SrcGeoIp(value.to_string())), "SRC-IP-ASN" => { let asn = value @@ -603,11 +561,9 @@ impl Rule { Ok(RuleType::SrcIpCidr(net)) } - // Port rules "DST-PORT" => Self::parse_port_or_range(value, false), "SRC-PORT" => Self::parse_port_or_range(value, true), - // Inbound rules "IN-PORT" => { let port = value .parse::() @@ -626,7 +582,6 @@ impl Rule { "IN-USER" => Ok(RuleType::InUser(value.to_string())), "IN-NAME" => Ok(RuleType::InName(value.to_string())), - // Process rules "PROCESS-PATH" => Ok(RuleType::ProcessPath(value.to_string())), "PROCESS-PATH-REGEX" => { let re = Regex::new(value).map_err(|e| RuleParseError::InvalidRegex(e.to_string()))?; @@ -644,7 +599,6 @@ impl Rule { Ok(RuleType::Uid(uid)) } - // Network rules "NETWORK" => match value.to_ascii_lowercase().as_str() { "tcp" => Ok(RuleType::Network(NetworkType::Tcp)), "udp" => Ok(RuleType::Network(NetworkType::Udp)), @@ -657,7 +611,6 @@ impl Rule { Ok(RuleType::Dscp(dscp)) } - // Advanced rules "RULE-SET" => Ok(RuleType::RuleSet(value.to_string())), // `iter().all(...)` returns true on an empty iterator, so an empty // AND would silently degenerate into a catch-all MATCH. Likewise @@ -797,10 +750,6 @@ impl Rule { } } -// ============================================================================ -// Display -// ============================================================================ - impl fmt::Display for NetworkType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -924,10 +873,6 @@ impl fmt::Debug for RuleType { } } -// ============================================================================ -// Helpers -// ============================================================================ - /// Convert a wildcard pattern (`*` = any chars, `?` = one char) into a /// case-insensitive anchored regex. Used at PARSE time so we don't recompile /// the regex on every match. Returns the compiled regex; the caller is @@ -1008,16 +953,10 @@ fn split_top_level(s: &str) -> Vec { parts } -// ============================================================================ -// Tests -// ============================================================================ - #[cfg(test)] mod tests { use super::*; - // -- Domain rules -- - #[test] fn parse_domain_exact() { let rule = Rule::parse("DOMAIN,example.com,REJECT").unwrap(); @@ -1119,8 +1058,6 @@ mod tests { assert!(rule.matches(&ctx)); } - // -- IP rules -- - #[test] fn parse_ip_cidr() { let rule = Rule::parse("IP-CIDR,192.168.0.0/16,DIRECT").unwrap(); @@ -1187,8 +1124,6 @@ mod tests { })); } - // -- Port rules -- - #[test] fn parse_dst_port() { let rule = Rule::parse("DST-PORT,443,PROXY").unwrap(); @@ -1211,8 +1146,6 @@ mod tests { })); } - // -- Inbound rules -- - #[test] fn parse_in_port() { let rule = Rule::parse("IN-PORT,1080,DIRECT").unwrap(); @@ -1274,8 +1207,6 @@ mod tests { })); } - // -- Process rules -- - #[test] fn parse_process_name() { let rule = Rule::parse("PROCESS-NAME,chrome,PROXY").unwrap(); @@ -1324,8 +1255,6 @@ mod tests { })); } - // -- Network rules -- - #[test] fn parse_network() { let rule = Rule::parse("NETWORK,tcp,PROXY").unwrap(); @@ -1352,16 +1281,12 @@ mod tests { })); } - // -- Match -- - #[test] fn parse_match_all() { let rule = Rule::parse("MATCH,FALLBACK").unwrap(); assert!(rule.matches(&MatchContext::default())); } - // -- Options -- - #[test] fn parse_with_options() { let rule = Rule::parse("IP-CIDR,10.0.0.0/8,DIRECT,no-resolve").unwrap(); @@ -1369,8 +1294,6 @@ mod tests { assert_eq!(rule.options, vec!["no-resolve"]); } - // -- Advanced: AND / OR / NOT -- - #[test] fn parse_and_rule() { let rule = Rule::parse("AND,((NETWORK,tcp),(DST-PORT,443)),PROXY").unwrap(); @@ -1423,8 +1346,6 @@ mod tests { })); } - // -- Multi-line parsing -- - #[test] fn parse_rules_multiline() { let rules = Rule::parse_rules( @@ -1440,8 +1361,6 @@ mod tests { assert!(rules.iter().all(Result::is_ok)); } - // -- Display round-trip -- - #[test] fn display_round_trip() { let input = "DOMAIN-SUFFIX,google.com,PROXY"; @@ -1449,8 +1368,6 @@ mod tests { assert_eq!(rule.to_string(), input); } - // -- Error cases -- - #[test] fn unknown_rule_type() { let err = Rule::parse("UNKNOWN,value,TARGET").unwrap_err(); @@ -1478,8 +1395,6 @@ mod tests { )); } - // -- Port range rules -- - #[test] fn parse_dst_port_range() { let rule = Rule::parse("DST-PORT,1000-2000,PROXY").unwrap(); @@ -1527,8 +1442,6 @@ mod tests { assert_eq!(rule2.to_string(), "SRC-PORT,100-200,DIRECT"); } - // -- Source IP rules -- - #[test] fn parse_src_ip_suffix() { let rule = Rule::parse("SRC-IP-SUFFIX,192.168.0.0/16,DIRECT").unwrap(); @@ -1578,8 +1491,6 @@ mod tests { assert!(rule.matches(&ctx)); } - // -- Process rules (regex) -- - #[test] fn parse_process_path_regex() { let rule = Rule::parse("PROCESS-PATH-REGEX,/usr/.*/curl,DIRECT").unwrap(); @@ -1593,8 +1504,6 @@ mod tests { })); } - // -- GeoIp with lookup -- - #[test] fn parse_geoip_with_lookup() { let rule = Rule::parse("GEOIP,US,PROXY").unwrap(); @@ -1619,8 +1528,6 @@ mod tests { assert!(rule.matches(&ctx)); } - // -- Compound rules edge cases -- - #[test] fn nested_and_or_compound() { // OR((NETWORK,tcp),(DST-PORT,53)) @@ -1654,16 +1561,12 @@ mod tests { })); } - // -- RuleSet and SubRule (placeholders) -- - #[test] fn rule_set_always_false() { let rule = Rule::parse("RULE-SET,my-set,PROXY").unwrap(); assert!(!rule.matches(&MatchContext::default())); } - // -- Case sensitivity -- - #[test] fn domain_case_insensitive() { let rule = Rule::parse("DOMAIN,Example.COM,REJECT").unwrap(); @@ -1686,8 +1589,6 @@ mod tests { })); } - // -- None field behavior -- - #[test] fn none_fields_never_match_specific_rules() { let rule = Rule::parse("DOMAIN,example.com,REJECT").unwrap(); @@ -1703,8 +1604,6 @@ mod tests { assert!(!rule.matches(&MatchContext::default())); // network is None } - // -- Display for compound rules -- - #[test] fn display_and_rule() { let rule = Rule::parse("AND,((NETWORK,tcp),(DST-PORT,443)),PROXY").unwrap(); @@ -1715,8 +1614,6 @@ mod tests { assert!(s.ends_with(",PROXY")); } - // -- Error cases -- - #[test] fn invalid_regex() { let err = Rule::parse("DOMAIN-REGEX,[invalid,TARGET").unwrap_err(); @@ -1748,13 +1645,13 @@ mod tests { /// PR4-C: an empty AND would silently become MATCH (every closure over an /// empty `Vec` returns true); empty OR would silently become a never-match. #[test] - fn pr4_empty_and_rejected_at_parse() { + fn empty_and_rejected_at_parse() { let err = Rule::parse("AND,(),TARGET").unwrap_err(); assert!(matches!(err, RuleParseError::InvalidFormat(_))); } #[test] - fn pr4_empty_or_rejected_at_parse() { + fn empty_or_rejected_at_parse() { let err = Rule::parse("OR,(),TARGET").unwrap_err(); assert!(matches!(err, RuleParseError::InvalidFormat(_))); } @@ -1762,7 +1659,7 @@ mod tests { /// PR4-H: a reversed port range matches nothing because `port >= lo` and /// `port <= hi` cannot both hold. Catch the misconfiguration at parse. #[test] - fn pr4_reversed_port_range_rejected() { + fn reversed_port_range_rejected() { let err = Rule::parse("DST-PORT,9000-1000,TARGET").unwrap_err(); assert!(matches!(err, RuleParseError::InvalidFormat(_))); let err = Rule::parse("SRC-PORT,9000-1000,TARGET").unwrap_err(); @@ -1774,7 +1671,7 @@ mod tests { /// into the IP-CIDR variant so the two keywords have provably-identical /// runtime behaviour. #[test] - fn pr4_ip_suffix_parses_as_ip_cidr() { + fn ip_suffix_parses_as_ip_cidr() { let r = Rule::parse("IP-SUFFIX,10.0.0.0/24,PROXY").unwrap(); assert!(matches!(r.rule_type, RuleType::IpCidr(_))); let r = Rule::parse("SRC-IP-SUFFIX,10.0.0.0/24,PROXY").unwrap(); @@ -1786,7 +1683,7 @@ mod tests { /// error instead of being deferred to every match call (where it used to /// silently fail-closed via `Regex::new(...).is_ok_and(...)`). #[test] - fn pr4_wildcard_compiles_at_parse() { + fn wildcard_compiles_at_parse() { let r = Rule::parse("DOMAIN-WILDCARD,*.example.com,PROXY").unwrap(); match r.rule_type { RuleType::DomainWildcard(p) => { @@ -1803,7 +1700,7 @@ mod tests { /// case at parse time so the per-match path can compare without /// allocating. #[test] - fn pr4_domain_suffix_keyword_lowercased() { + fn domain_suffix_keyword_lowercased() { let r = Rule::parse("DOMAIN-SUFFIX,Example.COM,DIRECT").unwrap(); match r.rule_type { RuleType::DomainSuffix(s) => assert_eq!(s, "example.com"), @@ -1820,7 +1717,7 @@ mod tests { /// the rest. After the fix it carries every parsed condition AND its /// `Display` form parses back to the same shape. #[test] - fn pr4_sub_rule_preserves_all_children() { + fn sub_rule_preserves_all_children() { let r = Rule::parse("SUB-RULE,((NETWORK,tcp),(DST-PORT,443)),PROXY").unwrap(); match &r.rule_type { RuleType::SubRule(rules, _) => assert_eq!(rules.len(), 2), diff --git a/crates/wind-core/src/types.rs b/crates/wind-core/src/types.rs index 22482ca..e636c61 100644 --- a/crates/wind-core/src/types.rs +++ b/crates/wind-core/src/types.rs @@ -49,7 +49,6 @@ impl<'de> Deserialize<'de> for TargetAddr { let s = String::deserialize(deserializer)?; - // Check if this is an IPv6 address with brackets [IPv6]:port if s.starts_with('[') { let end_bracket = match s.find(']') { Some(pos) => pos, @@ -58,7 +57,6 @@ impl<'de> Deserialize<'de> for TargetAddr { } }; - // Ensure there's a colon after the closing bracket if end_bracket + 1 >= s.len() || !s[end_bracket + 1..].starts_with(':') { return Err(Error::custom("Invalid IPv6 address format, expected [IPv6]:port")); } @@ -79,7 +77,6 @@ impl<'de> Deserialize<'de> for TargetAddr { return Ok(TargetAddr::IPv6(ipv6_addr, port)); } - // Split the string into host and port parts for IPv4 or domain. // `rsplit_once` matches the LAST ':' so multi-colon inputs (which the // IPv6 branch above didn't catch) fail cleanly instead of silently // taking only the first segment. @@ -93,10 +90,8 @@ impl<'de> Deserialize<'de> for TargetAddr { return Err(Error::custom("Invalid address: host part is empty")); } - // Parse the port let port = port_str.parse::().map_err(|_| Error::custom("Invalid port number"))?; - // Try to parse as IPv4 first if let Ok(ipv4) = host.parse::() { Ok(TargetAddr::IPv4(ipv4, port)) } else { @@ -192,20 +187,20 @@ mod tests { // deserialization, not silently produce a `Domain("", _)` etc. #[test] - fn pr4_deserialize_rejects_empty_host() { + fn deserialize_rejects_empty_host() { let result: Result = serde_json::from_str("\":80\""); let err = result.expect_err("`:80` must not parse — empty host"); assert!(err.to_string().to_lowercase().contains("empty")); } #[test] - fn pr4_deserialize_rejects_whitespace_in_domain() { + fn deserialize_rejects_whitespace_in_domain() { let result: Result = serde_json::from_str("\"x y:80\""); assert!(result.is_err(), "domain with whitespace must be rejected"); } #[test] - fn pr4_deserialize_uses_last_colon_for_split() { + fn deserialize_uses_last_colon_for_split() { // Without an IPv6 bracket form, an embedded `:` is ambiguous. The // hardened parser uses `rsplit_once(':')`, so the host part keeps any // leading colons; the validation step then catches the malformed diff --git a/crates/wind-core/src/udp_tests.rs b/crates/wind-core/src/udp_tests.rs index dee9c35..cebfb50 100644 --- a/crates/wind-core/src/udp_tests.rs +++ b/crates/wind-core/src/udp_tests.rs @@ -28,7 +28,6 @@ mod tests { .unwrap(); let dst_addr = recv.local_addr().unwrap(); - // Generate random data for testing let test_data = generate_random_data(data_len); test_send_recv( @@ -87,7 +86,6 @@ mod tests { let socket_state = UdpSocketState::new(sock.into()).expect("created socket state"); - // Change the send buffer size. let buffer_before = socket_state.send_buffer_size(sock.into()).unwrap(); assert_ne!( buffer_before, @@ -104,7 +102,6 @@ mod tests { "setting send buffer size to {BUFFER_SIZE} resulted in {buffer_before} -> {buffer_after}", ); - // Change the receive buffer size. let buffer_before = socket_state.recv_buffer_size(sock.into()).unwrap(); socket_state .set_recv_buffer_size(sock.into(), BUFFER_SIZE) diff --git a/crates/wind-dns/src/resolver.rs b/crates/wind-dns/src/resolver.rs index ebc0b63..fb86de7 100644 --- a/crates/wind-dns/src/resolver.rs +++ b/crates/wind-dns/src/resolver.rs @@ -112,8 +112,6 @@ pub(crate) fn build(cfg: &DnsConfig) -> Result> { Ok(Some(HickoryResolver::new(resolver, cfg.stack_prefer))) } -/// Factory: given a resolved IP and optional SNI, produce a -/// [`NameServerConfig`]. type MakeNameServer = fn(IpAddr, Option) -> NameServerConfig; /// Parse a DNS server URL or bare address into a [`NameServerConfig`]. diff --git a/crates/wind-naive/src/lib.rs b/crates/wind-naive/src/lib.rs index 2e236e2..12f9f97 100644 --- a/crates/wind-naive/src/lib.rs +++ b/crates/wind-naive/src/lib.rs @@ -38,10 +38,6 @@ fn to_cronet(cc: QuicCongestionControl) -> CronetCongestionControl { } } -// ============================================================================ -// Options -// ============================================================================ - /// Configuration for the Naive outbound. #[derive(Clone, Debug)] pub struct NaiveOutboundOpts { @@ -105,10 +101,6 @@ impl Default for NaiveOutboundOpts { } } -// ============================================================================ -// NaiveOutbound -// ============================================================================ - /// An outbound that tunnels traffic through a NaiveProxy server via Cronet. /// /// Uses `cronet-rs` (Chromium Cronet C API bindings) to establish HTTP/2 @@ -174,10 +166,6 @@ impl NaiveOutbound { } } -// ============================================================================ -// AbstractOutbound implementation -// ============================================================================ - impl AbstractOutbound for NaiveOutbound { async fn handle_tcp( &self, @@ -252,10 +240,6 @@ impl AbstractOutbound for NaiveOutbound { } } -// ============================================================================ -// UDP-over-TCP (UoT v2) bridge -// ============================================================================ - /// Bridge a [`UdpStream`] across a blocking /// [`cronet_rs::naive_conn::NaiveConn`] using UoT v2 framing. /// @@ -285,7 +269,6 @@ async fn naive_uot_bridge( let mut initial = uot::encode_request(&first.target)?; uot::encode_packet_into(&mut initial, &first.target, &first.payload)?; - // ── I/O thread (owns NaiveConn) ────────────────────────────────────── let io_handle = std::thread::Builder::new() .name("wind-naive-uot-io".into()) .spawn(move || { @@ -295,7 +278,6 @@ async fn naive_uot_bridge( let _ = naive.flush(); loop { - // Drain any queued uplink frames (non-blocking). let mut wrote = false; while let Ok(frame) = uplink_rx.try_recv() { if naive.write_all(&frame).is_err() { @@ -307,7 +289,6 @@ async fn naive_uot_bridge( let _ = naive.flush(); } - // Block on one downlink frame. match uot::read_packet(&mut naive) { Ok((source, payload)) => { let packet = UdpPacket { @@ -332,7 +313,6 @@ async fn naive_uot_bridge( }) .expect("spawn wind-naive-uot-io thread"); - // ── Uplink: async rx → framed bytes → io thread ────────────────────── let uplink = async move { while let Some(packet) = rx.recv().await { match uot::encode_packet(&packet.target, &packet.payload) { @@ -348,7 +328,6 @@ async fn naive_uot_bridge( } }; - // ── Downlink: io thread → async tx ─────────────────────────────────── let downlink = async move { while let Some(packet) = downlink_rx.recv().await { if tx.send(packet).await.is_err() { @@ -369,10 +348,6 @@ async fn naive_uot_bridge( Ok(()) } -// ============================================================================ -// Async bridge — blocking NaiveConn → async I/O -// ============================================================================ - /// Bridge data between a blocking [`cronet_rs::naive_conn::NaiveConn`] and a /// tokio [`AsyncRead`] + [`AsyncWrite`] stream. /// @@ -396,14 +371,12 @@ async fn naive_async_bridge( let (naive_write_tx, mut naive_write_rx) = mpsc::channel::>(NAIVE_BRIDGE_QUEUE); let (naive_read_tx, mut naive_read_rx) = mpsc::channel::>(NAIVE_BRIDGE_QUEUE); - // ── I/O thread (owns NaiveConn) ────────────────────────────── let io_handle = std::thread::Builder::new() .name("wind-naive-io".into()) .spawn(move || { let mut read_buf = [0u8; 65535]; loop { - // Drain pending writes. while let Ok(data) = naive_write_rx.try_recv() { if naive.write_all(&data).is_err() { return; @@ -411,7 +384,6 @@ async fn naive_async_bridge( let _ = naive.flush(); } - // Block on a read. match naive.read(&mut read_buf) { Ok(0) => { tracing::debug!("naive conn EOF"); @@ -434,7 +406,6 @@ async fn naive_async_bridge( }) .expect("spawn wind-naive-io thread"); - // ── Local I/O (async, select! across both directions) ──────── let mut local_buf = vec![0u8; 65535]; loop { @@ -478,15 +449,11 @@ async fn naive_async_bridge( .await } -// ============================================================================ -// libcronet loader -// ============================================================================ - /// Default search paths for `libcronet` (dynamic loading only). #[cfg(feature = "dynamic")] const CRONET_SEARCH_PATHS: &[&str] = &[ - "libcronet.so", // system LD_LIBRARY_PATH - "./libcronet.so", // CWD + "libcronet.so", + "./libcronet.so", "/usr/local/lib/libcronet.so", "/opt/cronet/libcronet.so", ]; @@ -547,10 +514,6 @@ fn load_cronet_dynamic(path: Option) -> eyre::Result<()> { )) } -// ============================================================================ -// Tests -// ============================================================================ - #[cfg(test)] mod tests { use super::*; diff --git a/crates/wind-quic/src/h3_adapter.rs b/crates/wind-quic/src/h3_adapter.rs index 79d21d1..dc3e233 100644 --- a/crates/wind-quic/src/h3_adapter.rs +++ b/crates/wind-quic/src/h3_adapter.rs @@ -97,10 +97,6 @@ fn stream_err(e: QuicError) -> StreamErrorIncoming { } } -// --------------------------------------------------------------------------- -// Recv stream — `PrefixedRecv` *is* the adapter's recv stream -// --------------------------------------------------------------------------- - /// [`PrefixedRecv`] doubles as the adapter's `h3::quic::RecvStream`: it already /// owns the (possibly empty) replayed prefix plus the backend recv stream, so /// no separate wrapper type is needed. Fresh accepted streams are wrapped with @@ -135,10 +131,6 @@ impl RecvStream for PrefixedRecv { } } -// --------------------------------------------------------------------------- -// Send stream -// --------------------------------------------------------------------------- - /// `h3::quic::SendStream` over the backend's send stream. pub struct H3Send { inner: C::SendStream, @@ -217,10 +209,6 @@ impl SendStream for H3Send { } } -// --------------------------------------------------------------------------- -// Bidi stream (request streams) -// --------------------------------------------------------------------------- - /// `h3::quic::BidiStream` joining an [`H3Send`] and a [`PrefixedRecv`]. pub struct H3Bidi { send: H3Send, @@ -281,10 +269,6 @@ fn into_bidi((send, recv): (C::SendStream, C::RecvStream)) -> } } -// --------------------------------------------------------------------------- -// Opener -// --------------------------------------------------------------------------- - /// Opens local uni/bidi streams (HTTP/3 control + QPACK streams). Produced by /// [`Connection::opener`]. pub struct H3Opener { @@ -310,10 +294,6 @@ impl OpenStreams for H3Opener { } } -// --------------------------------------------------------------------------- -// Connection -// --------------------------------------------------------------------------- - /// `h3::quic::Connection` over a [`QuicConnection`] handle. Accepts streams /// from the router's channels; opens streams directly on `conn`. pub struct H3Conn { @@ -374,10 +354,6 @@ impl Connection for H3Conn { } } -// --------------------------------------------------------------------------- -// Shared poll helpers -// --------------------------------------------------------------------------- - fn stream_id(id: u64) -> StreamId { // QUIC stream ids fit the h3 `StreamId` invariant (< 2^62); fall back to 0 // only if a backend ever surfaces something out of range. diff --git a/crates/wind-quic/src/quiche/driver.rs b/crates/wind-quic/src/quiche/driver.rs index 1410b0b..a720085 100644 --- a/crates/wind-quic/src/quiche/driver.rs +++ b/crates/wind-quic/src/quiche/driver.rs @@ -548,7 +548,6 @@ impl ApplicationOverQuic for BridgeDriver { let span = self.span.clone(); let _enter = span.enter(); - // Datagrams. loop { match qconn.dgram_recv(&mut self.buffer) { Ok(n) => { @@ -565,7 +564,6 @@ impl ApplicationOverQuic for BridgeDriver { .max_dgram .store(qconn.dgram_max_writable_len().unwrap_or(0), Ordering::Relaxed); - // Streams. let ids: Vec = qconn.readable().collect(); for sid in ids { if self.is_peer_initiated(sid) { @@ -587,13 +585,11 @@ impl ApplicationOverQuic for BridgeDriver { let _ = qconn.close(true, code as u64, &reason); } - // Keying-material exports. while let Some((out_len, label, context, reply)) = self.pending_exports.pop_front() { let res = export_keying_material(qconn, out_len, &label, &context); let _ = reply.send(res); } - // Local stream opens. while let Some(op) = self.pending_opens.pop_front() { match op { PendingOpen::Bi(reply) => { @@ -607,13 +603,11 @@ impl ApplicationOverQuic for BridgeDriver { } } - // Stream shutdowns. while let Some((sid, write, code)) = self.pending_shutdowns.pop_front() { let dir = if write { Shutdown::Write } else { Shutdown::Read }; let _ = qconn.stream_shutdown(sid, dir, code); } - // Outbound datagrams. while let Some(dg) = self.out_datagrams.front() { match qconn.dgram_send(dg.as_ref()) { Ok(()) => { @@ -627,7 +621,6 @@ impl ApplicationOverQuic for BridgeDriver { } } - // Per-stream sends + FINs. let sids: Vec = self.streams.keys().copied().collect(); for sid in &sids { self.write_stream(qconn, *sid); diff --git a/crates/wind-quic/src/quinn/mod.rs b/crates/wind-quic/src/quinn/mod.rs index 05eb00d..a0e4a31 100644 --- a/crates/wind-quic/src/quinn/mod.rs +++ b/crates/wind-quic/src/quinn/mod.rs @@ -27,10 +27,6 @@ use crate::{ traits::{QuicConnection, QuicRecvStream, QuicSendStream}, }; -// --------------------------------------------------------------------------- -// Streams -// --------------------------------------------------------------------------- - /// quinn send half. pub struct QuinnSend(quinn::SendStream); @@ -84,10 +80,6 @@ impl QuicRecvStream for QuinnRecv { } } -// --------------------------------------------------------------------------- -// Connection -// --------------------------------------------------------------------------- - /// A [`QuicConnection`] backed by quinn. /// /// For client connections an `Arc` is kept alive alongside the @@ -172,10 +164,6 @@ impl QuicConnection for QuinnConnection { } } -// --------------------------------------------------------------------------- -// Endpoint / connect -// --------------------------------------------------------------------------- - /// A quinn server endpoint that yields [`QuinnConnection`]s. pub struct QuinnAcceptor { endpoint: Endpoint, @@ -254,10 +242,6 @@ pub async fn connect( }) } -// --------------------------------------------------------------------------- -// Transport mapping -// --------------------------------------------------------------------------- - fn build_transport(t: &TransportConfig) -> Result { let mut tr = QuinnTransport::default(); let bidi = VarInt::from_u64(t.max_concurrent_bidi_streams) diff --git a/crates/wind-quic/tests/loopback.rs b/crates/wind-quic/tests/loopback.rs index 1a4ff05..7e6ec46 100644 --- a/crates/wind-quic/tests/loopback.rs +++ b/crates/wind-quic/tests/loopback.rs @@ -47,7 +47,6 @@ async fn run_case(server: C, client: C) { const CONTEXT: &[u8] = b"wind-quic-test-context"; let server_task = tokio::spawn(async move { - // 1. bidi echo. let (mut s_send, mut s_recv) = server.accept_bi().await.expect("accept_bi"); let mut buf = [0u8; 4]; s_recv.read_exact(&mut buf).await.expect("server read ping"); @@ -55,19 +54,16 @@ async fn run_case(server: C, client: C) { s_send.write_all(b"pong").await.expect("server write pong"); s_send.finish().expect("server finish"); - // 2. uni receive. let mut u_recv = server.accept_uni().await.expect("accept_uni"); let mut ubuf = Vec::new(); u_recv.read_to_end(&mut ubuf).await.expect("server read uni"); assert_eq!(ubuf.as_slice(), b"hello-uni"); - // 3. datagram echo (if supported). if server.max_datagram_size().is_some() { let dg = server.read_datagram().await.expect("server read datagram"); server.send_datagram(dg).expect("server echo datagram"); } - // 4. keying material. let mut km = [0u8; 32]; server .export_keying_material(&mut km, LABEL, CONTEXT) @@ -81,7 +77,6 @@ async fn run_case(server: C, client: C) { }); let client_km = { - // 1. bidi echo. let (mut c_send, mut c_recv) = client.open_bi().await.expect("open_bi"); c_send.write_all(b"ping").await.expect("client write ping"); c_send.finish().expect("client finish"); @@ -89,12 +84,10 @@ async fn run_case(server: C, client: C) { c_recv.read_exact(&mut buf).await.expect("client read pong"); assert_eq!(&buf, b"pong"); - // 2. uni send. let mut u_send = client.open_uni().await.expect("open_uni"); u_send.write_all(b"hello-uni").await.expect("client write uni"); u_send.finish().expect("client finish uni"); - // 3. datagram round-trip. if client.max_datagram_size().is_some() { client .send_datagram(Bytes::from_static(b"datagram-payload")) @@ -103,7 +96,6 @@ async fn run_case(server: C, client: C) { assert_eq!(&echoed[..], b"datagram-payload"); } - // 4. keying material. let mut km = [0u8; 32]; client .export_keying_material(&mut km, LABEL, CONTEXT) @@ -115,7 +107,6 @@ async fn run_case(server: C, client: C) { let server_km = server_task.await.expect("server task"); assert_eq!(server_km, client_km, "RFC 5705 keying material must match on both ends"); - // 5. close completes. client.close(0, b"done"); tokio::time::timeout(Duration::from_secs(2), client.closed()) .await diff --git a/crates/wind-socks/src/ext.rs b/crates/wind-socks/src/ext.rs index aab67aa..ea2d495 100644 --- a/crates/wind-socks/src/ext.rs +++ b/crates/wind-socks/src/ext.rs @@ -36,7 +36,6 @@ macro_rules! try_notify { } fn udp_bind_random_port(addr: Option) -> io::Result { - // Early return pattern: handle the Some case first if let Some(addr) = addr { let sock_addr = SocketAddr::new(addr, 0); let socket = Socket::new(Domain::for_address(sock_addr), Type::DGRAM, None)?; @@ -44,7 +43,6 @@ fn udp_bind_random_port(addr: Option) -> io::Result { return socket.set_nonblocking(true).map(|_| socket); } - // Handle None case (trying IPv6 first, then IPv4) const V4_UNSPEC: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); const V6_UNSPEC: SocketAddr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0); Socket::new(Domain::IPV6, Type::DGRAM, None) @@ -77,7 +75,6 @@ where let reply_port = peer_addr.as_socket().ok_or(SocksServerError::Bug("addr not IP"))?.port(); - // Respect the pre-populated reply IP address. let mut inner = proto.reply_success(SocketAddr::new(reply_ip, reply_port)).await?; let udp_fut = transfer(peer_sock); diff --git a/crates/wind-socks/src/lib.rs b/crates/wind-socks/src/lib.rs index 3e1cbcd..246f777 100644 --- a/crates/wind-socks/src/lib.rs +++ b/crates/wind-socks/src/lib.rs @@ -19,7 +19,6 @@ pub enum Error { backtrace: Backtrace, }, Io { - // action: String source: std::io::Error, backtrace: Backtrace, }, diff --git a/crates/wind-socks/src/outbound.rs b/crates/wind-socks/src/outbound.rs index dc4547f..4fe5de0 100644 --- a/crates/wind-socks/src/outbound.rs +++ b/crates/wind-socks/src/outbound.rs @@ -43,10 +43,8 @@ impl AbstractOutbound for SocksOutbound { }); if let Some(via) = via { - // Connect to the SOCKS server via the provided outbound let (local_stream, remote_stream) = tokio::io::duplex(8192); - // We need to run the via outbound handling in a background task let server_addr_target = match self.opts.server_addr.ip() { std::net::IpAddr::V4(v4) => TargetAddr::IPv4(v4, self.opts.server_addr.port()), std::net::IpAddr::V6(v6) => TargetAddr::IPv6(v6, self.opts.server_addr.port()), @@ -70,7 +68,6 @@ impl AbstractOutbound for SocksOutbound { res_via?; res_socks?; } else { - // Direct connection to the SOCKS server let tcp_stream = tokio::net::TcpStream::connect(self.opts.server_addr).await?; // Disable Nagle on the hop to the SOCKS proxy (small-write latency). if let Err(e) = tcp_stream.set_nodelay(true) { diff --git a/crates/wind-socks/src/udp.rs b/crates/wind-socks/src/udp.rs index 9ff1969..471136d 100644 --- a/crates/wind-socks/src/udp.rs +++ b/crates/wind-socks/src/udp.rs @@ -40,10 +40,8 @@ fn parse_udp_request_sync(data: &[u8]) -> Result<(u8, SocksTargetAddr, &[u8]), S let mut offset = 4; - // Parse target address based on address type let target_addr = match atyp { 0x01 => { - // IPv4 if data.len() < offset + 6 { return Err("Incomplete IPv4 address in SOCKS5 UDP header".into()); } @@ -53,7 +51,6 @@ fn parse_udp_request_sync(data: &[u8]) -> Result<(u8, SocksTargetAddr, &[u8]), S SocksTargetAddr::Ip(SocketAddr::V4(std::net::SocketAddrV4::new(ip, port))) } 0x03 => { - // Domain name if data.len() < offset + 1 { return Err("Incomplete domain length in SOCKS5 UDP header".into()); } @@ -71,7 +68,6 @@ fn parse_udp_request_sync(data: &[u8]) -> Result<(u8, SocksTargetAddr, &[u8]), S SocksTargetAddr::Domain(domain, port) } 0x04 => { - // IPv6 if data.len() < offset + 18 { return Err("Incomplete IPv6 address in SOCKS5 UDP header".into()); } diff --git a/crates/wind-test/src/socks5.rs b/crates/wind-test/src/socks5.rs index 1521794..16e697b 100644 --- a/crates/wind-test/src/socks5.rs +++ b/crates/wind-test/src/socks5.rs @@ -19,9 +19,6 @@ fn setup_crypto() { _ = rustls::crypto::ring::default_provider().install_default() } } -// ============================================================================= -// Public API - Direct Connection Tests (no proxy) -// ============================================================================= /// Basic TCP connection test using native Tokio (without proxy) /// Used to verify if the target server is reachable @@ -32,7 +29,6 @@ pub async fn test_direct_tcp(target_host: &str, target_port: u16) -> eyre::Resul let mut stream = TcpStream::connect(&addr).await?; println!("Connection successful!"); - // Send simple HTTP GET request let request = format!("GET / HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", target_host); stream.write_all(request.as_bytes()).await?; @@ -52,7 +48,6 @@ pub async fn test_direct_udp(target_host: &str, target_port: u16) -> eyre::Resul let socket = UdpSocket::bind(local_addr).await?; socket.connect(&addr).await?; - // Send simple DNS query let dns_query: Vec = vec![ 0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, @@ -68,10 +63,6 @@ pub async fn test_direct_udp(target_host: &str, target_port: u16) -> eyre::Resul Ok(()) } -// ============================================================================= -// Public API - SOCKS5 Proxy Tests -// ============================================================================= - pub async fn test_socks5_tcp(proxy_addr: &str, target_host: &str, target_port: u16) -> eyre::Result<()> { use fast_socks5::client::Socks5Stream; @@ -79,7 +70,6 @@ pub async fn test_socks5_tcp(proxy_addr: &str, target_host: &str, target_port: u println!("Proxy address: {}", proxy_addr); println!("Target address: {}:{}", target_host, target_port); - // Establish connection through SOCKS5 proxy (no authentication) let mut stream = Socks5Stream::connect( proxy_addr, target_host.to_string(), @@ -91,7 +81,6 @@ pub async fn test_socks5_tcp(proxy_addr: &str, target_host: &str, target_port: u println!("✓ Connected to target through proxy"); - // Send HTTP GET request let request = format!( "GET / HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nUser-Agent: wind-test/1.0\r\n\r\n", target_host @@ -100,13 +89,11 @@ pub async fn test_socks5_tcp(proxy_addr: &str, target_host: &str, target_port: u stream.flush().await?; println!("✓ HTTP request sent ({} bytes)", request.len()); - // Read response let mut response = Vec::new(); let bytes_read = stream.read_to_end(&mut response).await?; println!("✓ Response received: {} bytes", bytes_read); - // Parse and print response headers if bytes_read == 0 { return Err(eyre::eyre!("TCP proxy test failed: received zero bytes from target")); } @@ -135,13 +122,11 @@ pub async fn test_socks5_udp(proxy_addr: &str, target_host: &str, target_port: u println!("Proxy address: {}", proxy_addr); println!("Target address: {}:{}", target_host, target_port); - // Establish TCP connection to SOCKS5 proxy for UDP association let backing_socket = TcpStream::connect(proxy_addr) .await .map_err(|e| eyre::eyre!("Failed to connect to proxy: {}", e))?; println!("✓ TCP connection established with proxy"); - // Establish UDP association through SOCKS5 proxy (no authentication) // Use 127.0.0.1:0 to bind to any available interface and let the system choose // an available port let udp_socket_addr = "127.0.0.1:0".parse::()?; @@ -152,7 +137,6 @@ pub async fn test_socks5_udp(proxy_addr: &str, target_host: &str, target_port: u println!("✓ UDP association established through proxy"); println!("✓ Local UDP socket bound to: {}", socket.get_ref().local_addr()?); - // Prepare DNS query packet let dns_query: Vec = vec![ 0xAB, 0xCD, // Transaction ID 0x01, 0x00, // Flags: standard query @@ -167,11 +151,9 @@ pub async fn test_socks5_udp(proxy_addr: &str, target_host: &str, target_port: u println!("✓ DNS query prepared ({} bytes)", dns_query.len()); - // Send DNS query through SOCKS5 UDP socket.send_to(&dns_query, (target_host, target_port)).await?; println!("✓ DNS query sent through proxy"); - // Receive UDP response with timeout let mut buffer = vec![0u8; 1024]; match tokio::time::timeout(Duration::from_secs(5), socket.recv_from(&mut buffer)).await { Ok(Ok((len, _from_addr))) => { @@ -225,7 +207,6 @@ pub async fn test_socks5_udp_large_packet( println!("Target address: {}:{}", target_host, target_port); println!("Packet size: {} bytes", packet_size); - // Calculate if this packet will require fragmentation const IPV4_HEADER_SIZE: usize = 20; const UDP_HEADER_SIZE: usize = 8; const ETHERNET_MTU: usize = 1500; @@ -244,13 +225,11 @@ pub async fn test_socks5_udp_large_packet( ); } - // Establish TCP connection to SOCKS5 proxy for UDP association let backing_socket = TcpStream::connect(proxy_addr) .await .map_err(|e| eyre::eyre!("Failed to connect to proxy: {}", e))?; println!("✓ TCP connection established with proxy"); - // Establish UDP association through SOCKS5 proxy let udp_socket_addr = "127.0.0.1:0".parse::()?; let socket = Socks5Datagram::bind(backing_socket, udp_socket_addr) .await @@ -259,10 +238,8 @@ pub async fn test_socks5_udp_large_packet( println!("✓ UDP association established through proxy"); println!("✓ Local UDP socket bound to: {}", socket.get_ref().local_addr()?); - // Create a large test packet with a recognizable pattern let mut large_packet = Vec::with_capacity(packet_size); - // Add a header to identify the packet large_packet.extend_from_slice(b"WIND_FRAG_TEST"); large_packet.extend_from_slice(&(packet_size as u32).to_be_bytes()); @@ -283,7 +260,6 @@ pub async fn test_socks5_udp_large_packet( checksum = checksum.wrapping_add(*byte as u32); } - // Replace last 4 bytes with checksum if large_packet.len() >= 4 { let checksum_bytes = checksum.to_be_bytes(); let len = large_packet.len(); @@ -294,7 +270,6 @@ pub async fn test_socks5_udp_large_packet( println!(" Pattern: repeating '0123456789ABCDEF'"); println!(" Checksum: 0x{:08X}", checksum); - // Send large packet through SOCKS5 UDP let start_time = std::time::Instant::now(); socket.send_to(&large_packet, (target_host, target_port)).await?; let send_duration = start_time.elapsed(); @@ -303,7 +278,6 @@ pub async fn test_socks5_udp_large_packet( send_duration.as_secs_f64() * 1000.0 ); - // Receive UDP response with extended timeout for large packets let mut buffer = vec![0u8; packet_size + 1024]; // Extra buffer for potential overhead match tokio::time::timeout(Duration::from_secs(10), socket.recv_from(&mut buffer)).await { Ok(Ok((len, from_addr))) => { @@ -315,18 +289,15 @@ pub async fn test_socks5_udp_large_packet( receive_duration.as_secs_f64() * 1000.0 ); - // Verify the response if len == large_packet.len() { let received_data = &buffer[..len]; - // Check header if received_data.starts_with(b"WIND_FRAG_TEST") { println!("✓ Packet header verified"); } else { return Err(eyre::eyre!("Packet header mismatch")); } - // Verify checksum if len >= 4 { let received_checksum_bytes = &received_data[len - 4..]; let received_checksum = u32::from_be_bytes([ @@ -336,7 +307,6 @@ pub async fn test_socks5_udp_large_packet( received_checksum_bytes[3], ]); - // Calculate checksum of received data (excluding the checksum itself) let mut calc_checksum: u32 = 0; for byte in &received_data[..len - 4] { calc_checksum = calc_checksum.wrapping_add(*byte as u32); @@ -355,7 +325,6 @@ pub async fn test_socks5_udp_large_packet( } } - // Compare entire packet if received_data == large_packet { println!("✓ Complete packet integrity verified - fragmentation handled correctly"); } else { @@ -406,10 +375,6 @@ pub async fn test_socks5_udp_large_packet( Ok(()) } -// ============================================================================= -// Test Infrastructure - Proxy Server Setup -// ============================================================================= - /// Helper function to start the proxy server for testing /// /// This function creates a complete test proxy setup with: @@ -435,7 +400,6 @@ async fn start_test_proxy(socks_port: u16) -> eyre::Result<(Arc, config: TestConfig) -> use wind_core::{InboundCallback, inbound::AbstractInbound, tcp::AbstractTcpStream, types::TargetAddr}; - // Generate self-signed certificate for testing let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); let cert_der = cert.cert.der().to_vec(); let key_der = cert.key_pair.serialize_der(); - // Setup TUIC server options let tuic_opts = wind_tuic::quinn::inbound::TuicInboundOpts { listen_addr: format!("127.0.0.1:{}", config.tuic_port).parse()?, certificate: vec![rustls::pki_types::CertificateDer::from(cert_der)], @@ -494,7 +456,6 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> ..Default::default() }; - // Define the Manager struct for handling inbound connections #[derive(Clone)] struct TestManager { socks_inbound: Arc, @@ -503,27 +464,22 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> impl InboundCallback for TestManager { async fn handle_tcpstream(&self, target_addr: TargetAddr, stream: impl AbstractTcpStream) -> eyre::Result<()> { - // Direct connection - connect directly to target handle_tcp_direct(target_addr, stream).await?; Ok(()) } async fn handle_udpstream(&self, stream: wind_core::udp::UdpStream) -> eyre::Result<()> { - // Direct UDP relay handle_udp_direct(stream).await?; Ok(()) } } - // Helper function to handle TCP direct connection async fn handle_tcp_direct(target_addr: TargetAddr, mut inbound_stream: impl AbstractTcpStream) -> eyre::Result<()> { use tokio::io::AsyncWriteExt; - // Connect to target let addr = target_addr.to_string(); let mut outbound_stream = tokio::net::TcpStream::connect(&addr).await?; - // Bidirectional relay let (mut inbound_read, mut inbound_write) = tokio::io::split(&mut inbound_stream); let (mut outbound_read, mut outbound_write) = outbound_stream.split(); @@ -543,7 +499,6 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> Ok(()) } - // Helper function to handle UDP direct relay async fn handle_udp_direct(stream: wind_core::udp::UdpStream) -> eyre::Result<()> { use std::{collections::HashMap, sync::Arc}; @@ -557,7 +512,6 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> while let Some(packet) = rx.recv().await { let target_key = packet.target.to_string(); - // Get or create target socket let target_socket = { let mut sockets = target_sockets_clone.lock().await; if let Some(sock) = sockets.get(&target_key) { @@ -566,7 +520,6 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> let new_sock = Arc::new(tokio::net::UdpSocket::bind("0.0.0.0:0").await.unwrap()); sockets.insert(target_key.clone(), new_sock.clone()); - // Spawn receive task for this target let tx_for_recv = tx.clone(); let target_sock_for_recv = new_sock.clone(); let source_addr = packet.target.clone(); @@ -593,7 +546,6 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> } }; - // Send to target if let Err(e) = target_socket.send_to(&packet.payload, target_key).await { eprintln!("UDP relay: failed to send to target: {}", e); } @@ -604,7 +556,6 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> Ok(()) } - // Initialize inbound servers let tuic_inbound = Arc::new(wind_tuic::quinn::inbound::TuicInbound::new(ctx.clone(), tuic_opts)); let socks_inbound = Arc::new(wind_socks::inbound::SocksInbound::new( config.socks_opt, @@ -616,14 +567,12 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> tuic_inbound: tuic_inbound.clone(), }); - // Start TUIC inbound listening task let manager_for_tuic = manager.clone(); ctx.tasks.spawn(async move { manager_for_tuic.tuic_inbound.listen(manager_for_tuic.as_ref()).await?; eyre::Ok(()) }); - // Start SOCKS inbound listening task let manager_for_socks = manager.clone(); ctx.tasks.spawn(async move { manager_for_socks.socks_inbound.listen(manager_for_socks.as_ref()).await?; @@ -633,18 +582,10 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> Ok(()) } -// ============================================================================= -// Tests Module -// ============================================================================= - #[cfg(test)] mod tests { use super::*; - // ========================================================================= - // Basic Connection Tests - // ========================================================================= - #[tokio::test] async fn test_direct_tcp_connection() { let result = test_direct_tcp("example.com", 80).await; @@ -657,19 +598,13 @@ mod tests { assert!(result.is_ok(), "Direct UDP connection failed: {:?}", result.err()); } - // ========================================================================= - // Proxy Tests - Basic - // ========================================================================= - #[tokio::test] async fn test_tcp_through_proxy() { - // Start proxy server on a test port let test_port = 16666; let (ctx, _server_handle) = start_test_proxy(test_port).await.expect("Failed to start proxy"); let result = test_socks5_tcp(&format!("127.0.0.1:{}", test_port), "example.com", 80).await; - // Cleanup ctx.token.cancel(); let _ = tokio::time::timeout(Duration::from_secs(5), ctx.tasks.wait()).await; @@ -685,15 +620,12 @@ mod tests { use tokio::net::UdpSocket; - // Start proxy server on a test port let test_port = 16667; let (ctx, _server_handle) = start_test_proxy(test_port).await.expect("Failed to start proxy"); - // Start a local UDP echo server for testing let echo_server_running = Arc::new(AtomicBool::new(true)); let echo_server_running_clone = echo_server_running.clone(); - // Bind echo server to dynamic port let echo_socket = UdpSocket::bind("127.0.0.1:0") .await .expect("Failed to bind echo server socket"); @@ -702,7 +634,6 @@ mod tests { println!("✓ UDP Echo Server started on port {}", echo_port); - // Spawn the echo server task let echo_task = tokio::spawn(async move { let mut buffer = vec![0u8; 65536]; let mut packet_count = 0; @@ -715,7 +646,6 @@ mod tests { " Echo server received packet #{}: {} bytes from {}", packet_count, len, from_addr ); - // Echo the packet back if let Err(e) = echo_socket.send_to(&buffer[..len], from_addr).await { println!(" Echo server send error: {}", e); } @@ -730,20 +660,16 @@ mod tests { println!("✓ UDP Echo Server stopped after handling {} packets", packet_count); }); - // Give the echo server a moment to start tokio::time::sleep(Duration::from_millis(100)).await; - // Test UDP through proxy using local echo server let result = test_socks5_udp(&format!("127.0.0.1:{}", test_port), "127.0.0.1", echo_port).await; - // Signal the echo server to stop echo_server_running.store(false, Ordering::Relaxed); match tokio::time::timeout(Duration::from_secs(5), echo_task).await { Ok(_) => {} Err(_) => panic!("Echo server stop timeout in test_udp_through_proxy"), } - // Cleanup proxy ctx.token.cancel(); let _ = tokio::time::timeout(Duration::from_secs(5), ctx.tasks.wait()).await; @@ -762,10 +688,6 @@ mod tests { } } - // ========================================================================= - // Proxy Tests - Advanced (Large Packets & Fragmentation) - // ========================================================================= - #[tokio::test] async fn test_udp_large_packet_through_proxy() { use std::sync::{ @@ -775,20 +697,16 @@ mod tests { use tokio::net::UdpSocket; - // Start with a smaller packet to test basic functionality first let test_packet_size = 512; // Start small to ensure basic UDP works println!("Testing UDP functionality with {} byte packet", test_packet_size); - // Start proxy server on a test port let test_port = 16668; let (ctx, _server_handle) = start_test_proxy(test_port).await.expect("Failed to start proxy"); - // Start a UDP echo server in the background let echo_server_running = Arc::new(AtomicBool::new(true)); let echo_server_running_clone = echo_server_running.clone(); - // Find an available port for the echo server let echo_socket = UdpSocket::bind("127.0.0.1:0") .await .expect("Failed to bind echo server socket"); @@ -797,7 +715,6 @@ mod tests { println!("✓ UDP Echo Server started on port {}", echo_port); - // Spawn the echo server task let echo_task = tokio::spawn(async move { let mut buffer = vec![0u8; 65536]; // Large buffer for fragmented packets let mut packet_count = 0; @@ -811,7 +728,6 @@ mod tests { packet_count, len, from_addr ); - // Echo the packet back if let Err(e) = echo_socket.send_to(&buffer[..len], from_addr).await { println!(" Echo server send error: {}", e); } else { @@ -832,7 +748,6 @@ mod tests { println!("✓ UDP Echo Server stopped after handling {} packets", packet_count); }); - // Give the echo server a moment to start tokio::time::sleep(std::time::Duration::from_millis(100)).await; // First, test the echo server directly (without proxy) to ensure it works @@ -842,14 +757,12 @@ mod tests { Ok(_) => println!("✓ Direct UDP echo test passed"), Err(e) => { println!("✗ Direct UDP echo test failed: {}", e); - // Signal the echo server to stop echo_server_running.store(false, Ordering::Relaxed); let _ = tokio::time::timeout(std::time::Duration::from_secs(5), echo_task).await; panic!("Echo server is not working correctly"); } } - // Test small packet through proxy println!("\n=== Testing small packet through proxy (512 bytes) ==="); let result_small = test_socks5_udp_large_packet( &format!("127.0.0.1:{}", test_port), @@ -863,7 +776,6 @@ mod tests { Ok(_) => { println!("✓ Small packet test passed - now testing large packet"); - // If small packet works, try the large packet println!("\n=== Testing large packet through proxy (2000 bytes) ==="); let result_large = test_socks5_udp_large_packet( &format!("127.0.0.1:{}", test_port), @@ -894,29 +806,21 @@ mod tests { } } - // Signal the echo server to stop echo_server_running.store(false, Ordering::Relaxed); - // Wait for the echo server to finish (with timeout) match tokio::time::timeout(std::time::Duration::from_secs(5), echo_task).await { Ok(_) => println!("✓ Echo server stopped successfully"), Err(_) => panic!("Echo server stop timeout in test_udp_large_packet_through_proxy"), } - // Cleanup proxy server ctx.token.cancel(); let _ = tokio::time::timeout(Duration::from_secs(5), ctx.tasks.wait()).await; } - // ========================================================================= - // Test Helper Functions - // ========================================================================= - /// Helper function to test UDP echo server directly without proxy async fn test_direct_udp_with_echo_server(host: &str, port: u16, packet_size: usize) -> eyre::Result<()> { let test_socket = UdpSocket::bind("127.0.0.1:0").await?; - // Create test packet let mut test_packet = Vec::with_capacity(packet_size); test_packet.extend_from_slice(b"TEST_DIRECT"); test_packet.extend_from_slice(&(packet_size as u32).to_be_bytes()); @@ -927,10 +831,8 @@ mod tests { test_packet.push(byte); } - // Send packet test_socket.send_to(&test_packet, (host, port)).await?; - // Receive response let mut buffer = vec![0u8; packet_size + 100]; let (len, _) = tokio::time::timeout(std::time::Duration::from_secs(5), test_socket.recv_from(&mut buffer)).await??; @@ -957,7 +859,6 @@ mod tests { println!("=== UDP Fragmentation Demonstration ==="); println!("This test demonstrates UDP packet fragmentation without requiring a working SOCKS5 proxy"); - // Start a UDP echo server let echo_server_running = Arc::new(AtomicBool::new(true)); let echo_server_running_clone = echo_server_running.clone(); @@ -997,7 +898,6 @@ mod tests { tokio::time::sleep(std::time::Duration::from_millis(100)).await; - // Test different packet sizes to demonstrate fragmentation behavior let test_sizes = vec![ 512, // Small packet 1400, // Just under MTU @@ -1028,7 +928,6 @@ mod tests { ); } - // Test direct UDP (no proxy) to demonstrate that large packets work match test_direct_udp_with_echo_server("127.0.0.1", echo_port, size).await { Ok(_) => { println!("✓ Direct UDP test passed for {} bytes", size); @@ -1042,7 +941,6 @@ mod tests { } } - // Signal the echo server to stop echo_server_running.store(false, Ordering::Relaxed); let _ = tokio::time::timeout(std::time::Duration::from_secs(5), echo_task).await; } @@ -1056,7 +954,6 @@ mod tests { use tokio::net::UdpSocket; - // Start proxy server on a test port let test_port = 16669; let (ctx, _server_handle) = start_test_proxy(test_port).await.expect("Failed to start proxy"); @@ -1070,11 +967,9 @@ mod tests { 4000, // Much larger packet ]; - // Start a UDP echo server in the background let echo_server_running = Arc::new(AtomicBool::new(true)); let echo_server_running_clone = echo_server_running.clone(); - // Find an available port for the echo server let echo_socket = UdpSocket::bind("127.0.0.1:0") .await .expect("Failed to bind echo server socket"); @@ -1083,7 +978,6 @@ mod tests { println!("✓ UDP Echo Server started on port {} for MTU size testing", echo_port); - // Spawn the echo server task let echo_task = tokio::spawn(async move { let mut buffer = vec![0u8; 65536]; // Large buffer for fragmented packets let mut packet_count = 0; @@ -1097,7 +991,6 @@ mod tests { packet_count, len, from_addr ); - // Echo the packet back if let Err(e) = echo_socket.send_to(&buffer[..len], from_addr).await { println!(" Echo server send error: {}", e); } @@ -1116,19 +1009,12 @@ mod tests { println!("✓ UDP Echo Server stopped after handling {} packets", packet_count); }); - // Give the echo server a moment to start tokio::time::sleep(std::time::Duration::from_millis(100)).await; for size in test_sizes { println!("\n--- Testing packet size: {} bytes ---", size); - let result = test_socks5_udp_large_packet( - &format!("127.0.0.1:{}", test_port), - "127.0.0.1", - echo_port, // Use the dynamically allocated port - size, - ) - .await; + let result = test_socks5_udp_large_packet(&format!("127.0.0.1:{}", test_port), "127.0.0.1", echo_port, size).await; match &result { Ok(_) => println!("✓ Packet size {} bytes: SUCCESS", size), @@ -1136,10 +1022,8 @@ mod tests { let err_str = e.to_string(); if err_str.contains("Command not supported") { println!("⚠ SOCKS5 server does not support UDP ASSOCIATE - skipping remaining tests"); - // Signal the echo server to stop echo_server_running.store(false, Ordering::Relaxed); let _ = tokio::time::timeout(std::time::Duration::from_secs(5), echo_task).await; - // Cleanup proxy server ctx.token.cancel(); let _ = tokio::time::timeout(Duration::from_secs(5), ctx.tasks.wait()).await; panic!("UDP ASSOCIATE command not supported by SOCKS5 server"); @@ -1150,16 +1034,13 @@ mod tests { } } - // Signal the echo server to stop echo_server_running.store(false, Ordering::Relaxed); - // Wait for the echo server to finish (with timeout) match tokio::time::timeout(std::time::Duration::from_secs(5), echo_task).await { Ok(_) => println!("✓ Echo server stopped successfully"), Err(_) => panic!("Echo server stop timeout in test_udp_multiple_mtu_sizes"), } - // Cleanup proxy server ctx.token.cancel(); let _ = tokio::time::timeout(Duration::from_secs(5), ctx.tasks.wait()).await; } diff --git a/crates/wind-test/src/tuic.rs b/crates/wind-test/src/tuic.rs index d04d5e7..baab21c 100644 --- a/crates/wind-test/src/tuic.rs +++ b/crates/wind-test/src/tuic.rs @@ -20,10 +20,6 @@ use wind_core::{ udp::{UdpPacket, UdpStream}, }; -// ============================================================================= -// Public Test Helpers -// ============================================================================= - /// A simple [`InboundCallback`] that relays TCP connections directly to their /// targets and relays UDP packets to real targets via a bound UDP socket. /// @@ -137,10 +133,6 @@ pub fn generate_tuic_test_cert() -> (Vec>, PrivateKeyDer (vec![cert_der], PrivateKeyDer::Pkcs8(key_der)) } -// ============================================================================= -// Tests -// ============================================================================= - #[cfg(test)] mod tests { use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -155,10 +147,6 @@ mod tests { const TEST_PASSWORD: &[u8] = b"wind_tuic_test_secret"; - // ------------------------------------------------------------------------- - // Test setup helpers - // ------------------------------------------------------------------------- - struct TuicTestSetup { server_addr: SocketAddr, uuid: Uuid, @@ -235,10 +223,6 @@ mod tests { Ok(client) } - // ========================================================================= - // Connection & Authentication Tests - // ========================================================================= - /// A TUIC client must be able to establish a QUIC connection to the server. #[tokio::test] async fn test_tuic_connection() { @@ -308,17 +292,12 @@ mod tests { ); } - // ========================================================================= - // TCP Proxy Tests - // ========================================================================= - /// End-to-end TCP relay through TUIC: a message written on the local stream /// must reach the echo target and the echo must arrive back at the caller. #[tokio::test] async fn test_tuic_tcp_proxy() { use tokio::net::TcpListener; - // Start a simple TCP echo server let echo = TcpListener::bind("127.0.0.1:0").await.unwrap(); let echo_addr = echo.local_addr().unwrap(); tokio::spawn(async move { @@ -347,7 +326,6 @@ mod tests { let (mut local, remote) = tokio::io::duplex(4096); let target = TargetAddr::IPv4(std::net::Ipv4Addr::LOCALHOST, echo_addr.port()); - // Drive the TCP relay in a background task tokio::spawn(async move { let _ = client.handle_tcp(target, remote, None::).await; }); @@ -388,7 +366,6 @@ mod tests { let _ = client.handle_tcp(target, remote, None::).await; }); - // 32 KiB payload let payload: Vec = (0u8..=255).cycle().take(32 * 1024).collect(); local.write_all(&payload).await.expect("Write failed"); @@ -400,10 +377,6 @@ mod tests { assert_eq!(received, payload, "Large payload echo must match"); } - // ========================================================================= - // PR2 verification: per-fragment INFO-level logs must stay demoted - // ========================================================================= - /// PR2 regression test for log-level noise on the UDP fragmentation path. /// /// Before PR2 `crates/wind-tuic/src/proto/udp_stream.rs` emitted @@ -435,7 +408,7 @@ mod tests { /// every event regardless of target. #[tracing_test::traced_test] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_pr2_fragmented_udp_emits_no_info_log_noise() { + async fn test_fragmented_udp_emits_no_info_log_noise() { // (1) Spin up a UDP echo server on loopback. let echo = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap(); let echo_addr = echo.local_addr().unwrap(); @@ -515,7 +488,7 @@ mod tests { // filters captured lines by ` {span_name}:` (the test function name); // spawned tasks instrumented with `.in_current_span()` keep the // test's span as the current span, so their pre-formatted lines DO - // include `test_pr2_..._noise:` and pass the filter. + // include `test_..._noise:` and pass the filter. logs_assert(|lines: &[&str]| { for forbidden in ["Fragmentation params", "Sending fragment "] { if let Some(bad) = lines.iter().find(|l| l.contains(" INFO ") && l.contains(forbidden)) { diff --git a/crates/wind-tuic/src/proto/udp_stream.rs b/crates/wind-tuic/src/proto/udp_stream.rs index e943e87..c4c2407 100644 --- a/crates/wind-tuic/src/proto/udp_stream.rs +++ b/crates/wind-tuic/src/proto/udp_stream.rs @@ -22,7 +22,7 @@ pub struct UdpStream { connection: C, assoc_id: u16, receive_tx: UdpPacketTx, - next_pkt_id: AtomicU16, // Track packet IDs for fragmentation + next_pkt_id: AtomicU16, // Fragment reassembly state machine (backend-agnostic, from tuic-core). fragment_buffer: FragmentReassemblyBuffer, } @@ -53,7 +53,6 @@ impl UdpStream { } }; - // Calculate header overhead for single packet sending // Header (2 bytes) + Command (8 bytes) + Address let header_overhead = 10 + addr_size; // `saturating_sub` so that a transiently tiny `max_datagram_size` @@ -81,7 +80,6 @@ impl UdpStream { async fn send_fragmented_packet(&self, packet: UdpPacket) -> eyre::Result<()> { let payload_len = packet.payload.len(); - // Calculate address size for proper fragment size calculation let first_frag_addr_size = match packet.target { TargetAddr::IPv4(..) => 1 + 4 + 2, TargetAddr::IPv6(..) => 1 + 16 + 2, @@ -90,7 +88,6 @@ impl UdpStream { // Subsequent fragments use Address::None which is only 1 byte let subsequent_frag_addr_size = 1; - // Calculate max fragment payload size for first and subsequent fragments // Header (2 bytes) + Command (8 bytes) + Address let max_datagram_size = self.connection.max_datagram_size().unwrap_or(1200); let first_frag_header_overhead = 10 + first_frag_addr_size; @@ -125,7 +122,6 @@ impl UdpStream { subsequent_frag_max_payload, ); - // Calculate number of fragments needed // First fragment can hold first_frag_max_payload bytes // Each subsequent fragment can hold subsequent_frag_max_payload bytes let mut remaining_payload = payload_len; @@ -148,10 +144,8 @@ impl UdpStream { let frag_total = u8::try_from(fragment_count).map_err(|_| eyre::eyre!("Fragment count {} exceeds u8 range", fragment_count))?; - // Fragment and send each piece let mut offset = 0; for frag_id in 0..fragment_count { - // Calculate fragment size based on whether it's the first fragment or not let max_frag_payload = if frag_id == 0 { first_frag_max_payload } else { @@ -162,7 +156,6 @@ impl UdpStream { let fragment_size = remaining.min(max_frag_payload); let end = offset + fragment_size; - // Extract this fragment's payload let fragment_payload = packet.payload.slice(offset..end); // Allocate one buffer sized for header + payload so we can append @@ -174,7 +167,6 @@ impl UdpStream { }; let mut buf = BytesMut::with_capacity(header_overhead + fragment_payload.len()); - // Create packet command with fragmentation info HeaderCodec.encode(Header::new(CmdType::Packet), &mut buf)?; CmdCodec(CmdType::Packet).encode( Command::Packet { @@ -217,12 +209,10 @@ impl UdpStream { ); } - // Send using datagram self.connection .send_datagram(combined_payload) .map_err(|e| eyre::eyre!("Failed to send fragment: {}", e))?; - // Update offset for next fragment offset = end; } @@ -230,7 +220,6 @@ impl UdpStream { } /// Process an incoming packet fragment - /// This would be called by the packet handler in the TUIC protocol #[allow(clippy::too_many_arguments)] pub async fn process_fragment( &self, @@ -272,7 +261,6 @@ impl UdpStream { } pub async fn close(&mut self) -> Result<(), crate::Error> { - // Close the UDP association self.connection.drop_udp(self.assoc_id).await } } @@ -308,7 +296,6 @@ mod tests { let first_frag_max = MAX_DATAGRAM_SIZE - first_frag_overhead; let subsequent_frag_max = MAX_DATAGRAM_SIZE - subsequent_frag_overhead; - // Helper function to calculate fragment count let calc_frags = |payload_size: usize| -> usize { if payload_size <= first_frag_max { 1 @@ -318,7 +305,6 @@ mod tests { } }; - // Test various payload sizes let test_cases = vec![ (1000, 1), // Small payload, 1 fragment (first_frag_max, 1), // Exactly max size for first fragment, 1 fragment @@ -358,7 +344,6 @@ mod tests { // First fragment + 254 subsequent fragments let max_payload = first_frag_max + (subsequent_frag_max * (MAX_FRAGMENTS as usize - 1)); - // Calculate fragment count let remaining = max_payload - first_frag_max; let fragment_count = 1 + remaining.div_ceil(subsequent_frag_max); diff --git a/crates/wind-tuic/src/quinn/inbound.rs b/crates/wind-tuic/src/quinn/inbound.rs index 6b217fb..2a2ac0c 100644 --- a/crates/wind-tuic/src/quinn/inbound.rs +++ b/crates/wind-tuic/src/quinn/inbound.rs @@ -302,7 +302,6 @@ async fn handle_connection( conn }; - // Hand the established connection to the shared, backend-agnostic core. crate::server::serve_connection( QuinnConnection::new(conn), remote_addr, diff --git a/crates/wind-tuic/src/quinn/outbound.rs b/crates/wind-tuic/src/quinn/outbound.rs index 4e3ebd0..8c95fd9 100644 --- a/crates/wind-tuic/src/quinn/outbound.rs +++ b/crates/wind-tuic/src/quinn/outbound.rs @@ -108,7 +108,6 @@ impl TuicOutbound { } pub async fn start_poll(&self) -> eyre::Result<()> { - // Monitor cancellation token for shutdown let cancel_token = self.ctx.token.child_token(); let connection = self.connection.clone(); let udp_session = self.udp_session.clone(); @@ -153,10 +152,8 @@ impl TuicOutbound { } Ok(mut buf) = datagram_rx.recv() => { info!(target: "tuic_out", "Received datagram: {} bytes", buf.len()); - // Process the received datagram use bytes::Buf; - // Parse header, command, and address using helper functions let header = match crate::proto::decode_header(&mut buf, "datagram") { Ok(h) => h, Err(e) => { @@ -173,7 +170,6 @@ impl TuicOutbound { } }; - // Process UDP packet if let crate::proto::Command::Packet { assoc_id, pkt_id, @@ -181,7 +177,6 @@ impl TuicOutbound { frag_id, size, } = cmd { - // Parse address let addr = match crate::proto::decode_address(&mut buf, "UDP packet") { Ok(a) => a, Err(e) => { @@ -205,7 +200,6 @@ impl TuicOutbound { } let payload = buf.copy_to_bytes(size); - // Convert address to TargetAddr and handle logging let (target, has_address) = match crate::proto::address_to_target(addr) { Ok(t) => (t, true), Err(_) => { @@ -221,7 +215,6 @@ impl TuicOutbound { assoc_id, pkt_id, frag_id + 1, frag_total, size); } - // Find the corresponding UDP session if let Some(tuic_udp_stream) = udp_session.get(&assoc_id).await { let complete_packet = if frag_total > 1 { tuic_udp_stream.process_fragment(assoc_id, pkt_id, frag_total, frag_id, payload, None, target).await @@ -276,7 +269,6 @@ impl AbstractOutbound for TuicOutbound { _dialer: Option, ) -> eyre::Result<()> { use std::sync::atomic::Ordering; - // Create a cancel token for single udp session let cancel = self.token.child_token(); // Allocate a u16 association id, skipping ids that already have a live @@ -338,7 +330,6 @@ impl AbstractOutbound for TuicOutbound { info!(target: "tuic_out", "Received UDP packet forward to local (assoc {:#06x})", assoc_id); } } - // send queue packet = client_rx.recv() => { let packet = match packet { None => { @@ -357,7 +348,6 @@ impl AbstractOutbound for TuicOutbound { } } _ = gc_interval.tick() => { - // Perform garbage collection of expired fragments tuic_stream.collect_garbage().await; } } @@ -377,7 +367,6 @@ impl AbstractOutbound for TuicOutbound { } } - // Clean up the UDP association before exiting if let Err(err) = self.connection.drop_udp(assoc_id).await { info!(target: "tuic_out", "Error dropping UDP association {:#06x}: {}", assoc_id, err); } diff --git a/crates/wind-tuic/src/quinn/tls.rs b/crates/wind-tuic/src/quinn/tls.rs index e96fbd9..0a3e507 100644 --- a/crates/wind-tuic/src/quinn/tls.rs +++ b/crates/wind-tuic/src/quinn/tls.rs @@ -91,10 +91,6 @@ impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { } } -// --------------------------------------------------------------------------- -// PR1 regression tests -// --------------------------------------------------------------------------- - #[cfg(test)] mod tests { use std::{net::SocketAddr, sync::Arc, time::Duration}; diff --git a/crates/wind-tuic/src/server/mod.rs b/crates/wind-tuic/src/server/mod.rs index 1428951..a9d75b9 100644 --- a/crates/wind-tuic/src/server/mod.rs +++ b/crates/wind-tuic/src/server/mod.rs @@ -592,7 +592,6 @@ async fn handle_bi_stream( info!(target = %target_addr, "TCP connect"); - // Join the recv/send halves into one duplex stream for the relay. let stream = tokio::io::join(recv, send); callback.handle_tcpstream(target_addr, stream).await?; @@ -931,7 +930,6 @@ async fn read_address_exact(recv: &mut R) -> eyre::Result< } } -/// Handle UDP dissociate async fn handle_dissociate(connection: &InboundCtx, assoc_id: u16) -> eyre::Result<()> { connection.udp_sessions.remove(&assoc_id).await; info!("Dissociated UDP session {}", assoc_id); diff --git a/crates/wind/src/conf/persistent.rs b/crates/wind/src/conf/persistent.rs index 082a19e..f6b6220 100644 --- a/crates/wind/src/conf/persistent.rs +++ b/crates/wind/src/conf/persistent.rs @@ -7,10 +7,6 @@ use figment::{ use serde::{Deserialize, Serialize}; use uuid::Uuid; -// ============================================================================ -// Top-level config -// ============================================================================ - /// Root configuration for Wind. /// /// # Example (YAML) @@ -37,10 +33,6 @@ pub struct PersistentConfig { pub outbounds: Vec, } -// ============================================================================ -// Default -// ============================================================================ - impl Default for PersistentConfig { fn default() -> Self { Self { @@ -69,10 +61,6 @@ impl Default for PersistentConfig { } } -// ============================================================================ -// Inbounds -// ============================================================================ - /// One inbound protocol instance. #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "type")] @@ -131,10 +119,6 @@ pub enum AuthConfig { }, } -// ============================================================================ -// Outbounds -// ============================================================================ - /// One outbound protocol instance. #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "type")] @@ -146,8 +130,6 @@ pub enum OutboundConfig { Naive(NaiveOutboundConfig), } -// ── TUIC ───────────────────────────────────────────────────────────────── - #[derive(Debug, Deserialize, Serialize)] pub struct TuicOutboundConfig { /// Tag (name) used by the router to select this outbound. @@ -190,8 +172,6 @@ pub struct TuicOutboundConfig { pub alpn: Vec, } -// ── NaiveProxy ─────────────────────────────────────────────────────────── - #[derive(Debug, Deserialize, Serialize)] pub struct NaiveOutboundConfig { /// Tag (name) used by the router. @@ -233,10 +213,6 @@ pub struct NaiveOutboundConfig { pub cronet_lib_path: Option, } -// ============================================================================ -// Default helpers -// ============================================================================ - fn default_true() -> bool { true } @@ -256,10 +232,6 @@ fn default_concurrency() -> u32 { 1 } -// ============================================================================ -// Config loader -// ============================================================================ - impl PersistentConfig { /// Write the default config to a file. pub fn export_to_file(&self, file_path: &PathBuf, format: &str) -> eyre::Result<()> { @@ -324,10 +296,6 @@ impl PersistentConfig { } } -// ============================================================================ -// PR1 regression tests -// ============================================================================ - #[cfg(test)] mod tests { use super::*; @@ -383,7 +351,7 @@ password: "p" /// parser, producing cryptic "expected `[section]`" errors when the file /// was actually JSON or had a typo. Now they fail loudly up front. #[test] - fn pr4_unknown_config_extension_rejected() { + fn unknown_config_extension_rejected() { let err = PersistentConfig::load(Some("/tmp/wind-pr4-bogus.json".into()), None) .expect_err("`.json` is not supported and must be rejected"); let msg = format!("{err:#}"); diff --git a/crates/wind/src/conf/runtime.rs b/crates/wind/src/conf/runtime.rs index b1fc509..9283da3 100644 --- a/crates/wind/src/conf/runtime.rs +++ b/crates/wind/src/conf/runtime.rs @@ -3,10 +3,6 @@ pub use wind_tuic::quinn::outbound::TuicOutboundOpts; use crate::conf::persistent::{AuthConfig, InboundConfig, OutboundConfig, PersistentConfig}; -// ============================================================================ -// Runtime inbounds -// ============================================================================ - /// A fully-resolved inbound ready to be started. pub struct InboundRuntime { pub tag: String, @@ -40,10 +36,6 @@ impl InboundRuntime { } } -// ============================================================================ -// Runtime outbounds -// ============================================================================ - /// A fully-resolved outbound ready to be started. pub struct OutboundRuntime { pub tag: String, @@ -99,10 +91,6 @@ impl OutboundRuntime { } } -// ============================================================================ -// Top-level runtime config -// ============================================================================ - pub struct Config { pub inbounds: Vec, pub outbounds: Vec, @@ -117,10 +105,6 @@ impl Config { } } -// ============================================================================ -// Helpers -// ============================================================================ - fn parse_socket_addr(s: &str) -> std::net::SocketAddr { s.parse() .expect("server_addr must be a valid socket address (e.g. \"127.0.0.1:9443\")") diff --git a/crates/wind/src/main.rs b/crates/wind/src/main.rs index 2868926..a182d9a 100644 --- a/crates/wind/src/main.rs +++ b/crates/wind/src/main.rs @@ -13,10 +13,6 @@ use wind_tuic::quinn::outbound::TuicOutbound; use crate::conf::runtime::{InboundOpts, InboundRuntime, OutboundOpts, OutboundRuntime}; -// ============================================================================ -// Inbound enum — dyn-compatible wrapper over AbstractInbound -// ============================================================================ - enum InboundHandle { Socks(SocksInbound), } @@ -29,10 +25,7 @@ impl AbstractInbound for InboundHandle { } } -// ============================================================================ // Router — always forwards to the first outbound (TODO: ACL rules) -// ============================================================================ - struct DefaultRouter { default: String, } @@ -47,10 +40,6 @@ impl Router for DefaultRouter { } } -// ============================================================================ -// Manager — one inbound + shared dispatcher -// ============================================================================ - struct Manager { inbound: InboundHandle, dispatcher: Arc>, @@ -63,20 +52,12 @@ impl Manager { } } -// ============================================================================ -// Modules -// ============================================================================ - mod util; use crate::{cli::Cli, conf::persistent::PersistentConfig}; mod cli; mod conf; mod log; -// ============================================================================ -// main -// ============================================================================ - // curl --socks5 127.0.0.1:6666 https://www.bing.com #[tokio::main] async fn main() -> eyre::Result<()> { @@ -130,11 +111,9 @@ async fn main() -> eyre::Result<()> { let runtime_config = conf::runtime::Config::from_persist(persistent_config); let ctx = Arc::new(AppContext::default()); - // ── Build outbounds & dispatcher ─────────────────────────────────── let dispatcher = build_dispatcher(runtime_config.outbounds, ctx.clone()).await?; let dispatcher = Arc::new(dispatcher); - // ── Start inbounds ───────────────────────────────────────────────── for ib in runtime_config.inbounds { start_inbound(ib, &dispatcher, &ctx).await?; } @@ -149,10 +128,6 @@ async fn main() -> eyre::Result<()> { Ok(()) } -// ============================================================================ -// Boot helpers -// ============================================================================ - async fn build_dispatcher(outbounds: Vec, ctx: Arc) -> eyre::Result> { let default_tag = outbounds.first().map(|o| o.tag.clone()).unwrap_or_else(|| "default".into());