From 3e1d2b0da17cef44105a9b6b1569d103de6dcf1f Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Thu, 28 Mar 2024 20:00:07 +0800 Subject: [PATCH 1/8] update rustls & tokio-rustls deps, add utls feature; add shadows-tls impl --- Cargo.lock | 81 ++- Cargo.toml | 6 +- clash_lib/Cargo.toml | 1 + clash_lib/src/app/inbound/network_listener.rs | 5 +- clash_lib/src/proxy/converters/shadowsocks.rs | 9 + clash_lib/src/proxy/shadowsocks/mod.rs | 39 ++ .../proxy/shadowsocks/shadow_tls/connector.rs | 161 ++++++ .../src/proxy/shadowsocks/shadow_tls/mod.rs | 6 + .../proxy/shadowsocks/shadow_tls/stream.rs | 508 ++++++++++++++++++ .../src/proxy/shadowsocks/shadow_tls/utils.rs | 166 ++++++ clash_lib/src/proxy/utils/socket_helpers.rs | 1 + 11 files changed, 939 insertions(+), 44 deletions(-) create mode 100644 clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs create mode 100644 clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs create mode 100644 clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs create mode 100644 clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs diff --git a/Cargo.lock b/Cargo.lock index a18ea3cf..986bb974 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,9 +822,9 @@ checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "by_address" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8dba2868114ed769a1f2590fc9ae5eb331175b44313b6c9b922f8f7ca813d0" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "byte_string" @@ -1132,13 +1132,14 @@ dependencies = [ "rand", "regex", "register-count", - "rustls 0.21.10", + "rustls 0.21.8", "rustls-pemfile", "security-framework", "serde", "serde_json", "serde_yaml", "serial_test", + "sha1", "sha2", "shadowsocks", "smoltcp", @@ -1629,11 +1630,11 @@ dependencies = [ [[package]] name = "derive-adhoc" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc3f185a1a0d933e54447b3492fe9880db2c3ad37c04b3a3bf9b24a73b7bd02" +checksum = "0c768757217a40364c3af0b732d10bf9ed8af1e4f80b32fd5eeed94f375304b8" dependencies = [ - "derive-adhoc-macros 0.8.3", + "derive-adhoc-macros 0.8.4", "heck 0.4.1", ] @@ -1656,9 +1657,9 @@ dependencies = [ [[package]] name = "derive-adhoc-macros" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f1a082460419acefc6a66ff74c6e15c460d9b95530697f5d10892ee624aa60" +checksum = "3b41d3d974cda1ce7e9b40f7ec5160a099f20345bc3137447e526e3ba9d63245" dependencies = [ "heck 0.4.1", "itertools 0.12.1", @@ -2515,7 +2516,7 @@ dependencies = [ "once_cell", "rand", "ring 0.16.20", - "rustls 0.21.10", + "rustls 0.21.8", "rustls-pemfile", "serde", "thiserror", @@ -2541,7 +2542,7 @@ dependencies = [ "parking_lot 0.12.1", "rand", "resolv-conf", - "rustls 0.21.10", + "rustls 0.21.8", "serde", "smallvec", "thiserror", @@ -2565,7 +2566,7 @@ dependencies = [ "hickory-proto", "hickory-resolver", "http 0.2.12", - "rustls 0.21.10", + "rustls 0.21.8", "serde", "thiserror", "time", @@ -2778,7 +2779,7 @@ dependencies = [ "http 0.2.12", "hyper 0.14.28", "log", - "rustls 0.21.10", + "rustls 0.21.8", "rustls-native-certs", "tokio", "tokio-rustls", @@ -3288,9 +3289,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -3980,9 +3981,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "plotters" @@ -4262,7 +4263,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.21.10", + "rustls 0.21.8", "thiserror", "tokio", "tracing", @@ -4278,7 +4279,7 @@ dependencies = [ "rand", "ring 0.16.20", "rustc-hash", - "rustls 0.21.10", + "rustls 0.21.8", "slab", "thiserror", "tinyvec", @@ -4420,7 +4421,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4440,7 +4441,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4451,9 +4452,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "register-count" @@ -4608,9 +4609,8 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +version = "0.21.8" +source = "git+https://github.com/Watfaq/rustls.git?rev=43ecd5c610741668488e6d57857f9900a2087a5b#43ecd5c610741668488e6d57857f9900a2087a5b" dependencies = [ "log", "ring 0.17.8", @@ -4654,9 +4654,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" @@ -5543,10 +5543,9 @@ dependencies = [ [[package]] name = "tokio-rustls" version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +source = "git+https://github.com/Watfaq/tokio-rustls.git?rev=fcda89f6348c1e696b239bc7e0b168015cfb8c08#fcda89f6348c1e696b239bc7e0b168015cfb8c08" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.8", "tokio", ] @@ -5918,7 +5917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c579e92f3b9e419e68cd317d33f567491365b81f943b063d30f32e4a2f072c5" dependencies = [ "config", - "derive-adhoc 0.8.3", + "derive-adhoc 0.8.4", "derive_builder_fork_arti", "directories", "educe", @@ -6096,7 +6095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4613dfe9d946db3b5769b860a16598a9c4a0f85df21653d0765b1238106d297" dependencies = [ "async-trait", - "derive-adhoc 0.8.3", + "derive-adhoc 0.8.4", "derive_more", "educe", "either", @@ -6164,7 +6163,7 @@ checksum = "365c02c66f2f0159078714dd44947fb06c76956a3621fc102783119e5093be96" dependencies = [ "amplify", "arrayvec", - "derive-adhoc 0.8.3", + "derive-adhoc 0.8.4", "derive_builder_fork_arti", "derive_more", "downcast-rs", @@ -6346,7 +6345,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4bbf6c0a786daab669a75ec7380ae57f9aba91f2c4ea05a12e596d9bc6b49a0" dependencies = [ - "derive-adhoc 0.8.3", + "derive-adhoc 0.8.4", "derive_more", "filetime", "fs-mistrust", @@ -6448,7 +6447,7 @@ dependencies = [ "amplify", "async-trait", "backtrace", - "derive-adhoc 0.8.3", + "derive-adhoc 0.8.4", "derive_more", "educe", "futures", @@ -7415,27 +7414,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index a348c331..d8a2a56d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,8 @@ edition = "2021" opt-level = "s" codegen-units = 1 lto = true -strip = true \ No newline at end of file +strip = true + +[patch.crates-io] +tokio-rustls = { git = "https://github.com/Watfaq/tokio-rustls.git", rev = "fcda89f6348c1e696b239bc7e0b168015cfb8c08"} +rustls = { git = "https://github.com/Watfaq/rustls.git", rev = "43ecd5c610741668488e6d57857f9900a2087a5b"} diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index 80e2a445..7a512e92 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -52,6 +52,7 @@ opentelemetry-otlp = { version = "0.15.0", features = ["http-proto"] } crc32fast = "1.4.0" brotli = "3.5.0" hmac = "0.12.1" +sha1 = "0.10" sha2 = "0.10.8" md-5 = "0.10.5" chacha20poly1305 = "0.10" diff --git a/clash_lib/src/app/inbound/network_listener.rs b/clash_lib/src/app/inbound/network_listener.rs index 9d3db7ea..34642f76 100644 --- a/clash_lib/src/app/inbound/network_listener.rs +++ b/clash_lib/src/app/inbound/network_listener.rs @@ -12,7 +12,7 @@ use tracing::{info, warn}; use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; -#[derive(Eq, PartialEq, Hash)] +#[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ListenerType { Http, Socks5, @@ -114,13 +114,14 @@ impl NetworkInboundListener { }; if listener.handle_tcp() { + let listener_type = self.listener_type.clone(); info!("{} TCP listening at: {}:{}", self.name, ip, self.port); let tcp_listener = listener.clone(); runners.push( async move { tcp_listener.listen_tcp().await.map_err(|e| { - warn!("handler tcp listen failed: {}", e); + warn!("handler of {:?} tcp listen failed: {}", listener_type, e); e.into() }) } diff --git a/clash_lib/src/proxy/converters/shadowsocks.rs b/clash_lib/src/proxy/converters/shadowsocks.rs index b916650c..02adb2f9 100644 --- a/clash_lib/src/proxy/converters/shadowsocks.rs +++ b/clash_lib/src/proxy/converters/shadowsocks.rs @@ -46,6 +46,15 @@ impl TryFrom<&OutboundShadowsocks> for AnyOutboundHandler { .try_into() .map(OBFSOption::V2Ray) .ok(), + "shadow-tls" => s + .plugin_opts + .clone() + .ok_or(Error::InvalidConfig( + "plugin_opts is required for plugin obfs".to_owned(), + ))? + .try_into() + .map(OBFSOption::ShadowTls) + .ok(), _ => { return Err(Error::InvalidConfig(format!( "unsupported plugin: {}", diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index ade423be..64dccd91 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -1,4 +1,5 @@ mod datagram; +mod shadow_tls; mod simple_obfs; mod stream; mod v2ray; @@ -129,9 +130,42 @@ impl TryFrom> for V2RayOBFSOption { } } +#[derive(Debug)] +pub struct ShadowTlsOption { + pub host: String, + pub password: String, + pub strict: bool, +} + +impl TryFrom> for ShadowTlsOption { + type Error = crate::Error; + + fn try_from(value: HashMap) -> Result { + let host = value + .get("host") + .and_then(|x| x.as_str()) + .unwrap_or("bing.com"); + let password = value + .get("password") + .and_then(|x| x.as_str().to_owned()) + .ok_or(Error::InvalidConfig("obfs mode is required".to_owned()))?; + let strict = value + .get("strict") + .and_then(|x| x.as_bool()) + .unwrap_or(true); + + Ok(Self { + host: host.to_string(), + password: password.to_string(), + strict, + }) + } +} + pub enum OBFSOption { Simple(SimpleOBFSOption), V2Ray(V2RayOBFSOption), + ShadowTls(ShadowTlsOption), } pub struct HandlerOptions { @@ -227,6 +261,11 @@ impl OutboundHandler for Handler { OBFSOption::V2Ray(_opt) => { todo!("v2ray-plugin is not implemented yet") } + OBFSOption::ShadowTls(opts) => { + tracing::debug!("using shadow-tls with option: {:?}", opts); + let res = shadow_tls::Connector::wrap(opts, s).await?; + res + } }, None => s, }; diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs new file mode 100644 index 00000000..86918f90 --- /dev/null +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs @@ -0,0 +1,161 @@ +use std::io; +use std::ptr::copy_nonoverlapping; +use std::sync::Arc; + +use rand::Rng; + +use rand::distributions::Distribution; +use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio_rustls::client::TlsStream; +use tokio_rustls::rustls::ClientConfig; +use tokio_rustls::TlsConnector; + +use crate::proxy::shadowsocks::ShadowTlsOption; +use crate::proxy::AnyStream; + +use super::prelude::*; + +use super::stream::{ProxyTlsStream, VerifiedStream}; +use super::utils::Hmac; + +#[derive(Clone, Debug)] +#[allow(unused)] +pub struct Opts { + pub fastopen: bool, + pub sni: String, + pub strict: bool, +} + +pub struct Connector { + pub password: String, + pub server_addr: String, + pub tls_config: ClientConfig, + pub connector: TlsConnector, +} + +impl Connector { + fn connector() -> TlsConnector { + use crate::common::tls::GLOBAL_ROOT_STORE; + + let tls_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(GLOBAL_ROOT_STORE.clone()) + .with_no_client_auth(); + let connector = TlsConnector::from(Arc::new(tls_config.clone())); + connector + } + + pub async fn wrap(opts: &ShadowTlsOption, stream: AnyStream) -> std::io::Result { + let proxy_stream = ProxyTlsStream::new(stream, &opts.password); + + tracing::trace!("tcp connected, start handshaking"); + + let hamc_handshake = Hmac::new(&opts.password, (&[], &[])); + let sni_name = rustls::ServerName::try_from(opts.host.as_str()) + .unwrap_or_else(|_| panic!("invalid server name: {}", opts.host)); + let session_id_generator = move |data: &_| generate_session_id(&hamc_handshake, data); + let connector = Self::connector(); + let mut tls = connector + .connect_with(sni_name, proxy_stream, Some(session_id_generator), |_| {}) + .await?; + // perform a fake request, will do the handshake + tracing::trace!("handshake done"); + + let authorized = tls.get_mut().0.authorized(); + let maybe_server_random_and_hamc = tls + .get_mut() + .0 + .state() + .as_ref() + .map(|s| (s.server_random, s.hmac.to_owned())); + + if (!authorized || maybe_server_random_and_hamc.is_none()) && opts.strict { + tracing::warn!("V3 strict enabled: traffic hijacked or TLS1.3 is not supported, perform fake request"); + + tls.get_mut().0.fake_request = true; + + let r = fake_request(tls).await; + + return Err(io::Error::new(io::ErrorKind::Other, format!("V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request, res:{:?}", r))); + } + + let (server_random, hmac_nop) = match maybe_server_random_and_hamc { + Some(inner) => inner, + None => { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("server random and hmac not extracted from handshake, fail to connect"), + )); + } + }; + + let hmac_client = Hmac::new(&opts.password, (&server_random, "C".as_bytes())); + let hmac_server = Hmac::new(&opts.password, (&server_random, "S".as_bytes())); + + let verified_stream = VerifiedStream::new( + tls.into_inner().0.raw, + hmac_client, + hmac_server, + Some(hmac_nop), + ); + + return Ok(Box::new(verified_stream)); + } +} + +/// Take a slice of tls message[5..] and returns signed session id. +/// +/// Only used by V3 protocol. +fn generate_session_id(hmac: &Hmac, buf: &[u8]) -> [u8; TLS_SESSION_ID_SIZE] { + /// Note: SESSION_ID_START does not include 5 TLS_HEADER_SIZE. + const SESSION_ID_START: usize = 1 + 3 + 2 + TLS_RANDOM_SIZE + 1; + + if buf.len() < SESSION_ID_START + TLS_SESSION_ID_SIZE { + tracing::warn!("unexpected client hello length"); + return [0; TLS_SESSION_ID_SIZE]; + } + + let mut session_id = [0; TLS_SESSION_ID_SIZE]; + rand::thread_rng().fill(&mut session_id[..TLS_SESSION_ID_SIZE - HMAC_SIZE]); + let mut hmac = hmac.to_owned(); + hmac.update(&buf[0..SESSION_ID_START]); + hmac.update(&session_id); + hmac.update(&buf[SESSION_ID_START + TLS_SESSION_ID_SIZE..]); + let hmac_val = hmac.finalize(); + unsafe { + copy_nonoverlapping( + hmac_val.as_ptr(), + session_id.as_mut_ptr().add(TLS_SESSION_ID_SIZE - HMAC_SIZE), + HMAC_SIZE, + ) + } + session_id +} + +/// Doing fake request. +/// +/// Only used by V3 protocol. +async fn fake_request( + mut stream: TlsStream, +) -> std::io::Result<()> { + const HEADER: &[u8; 207] = b"GET / HTTP/1.1\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36\nAccept: gzip, deflate, br\nConnection: Close\nCookie: sessionid="; + const FAKE_REQUEST_LENGTH_RANGE: (usize, usize) = (16, 64); + let cnt = + rand::thread_rng().gen_range(FAKE_REQUEST_LENGTH_RANGE.0..FAKE_REQUEST_LENGTH_RANGE.1); + let mut buffer = Vec::with_capacity(cnt + HEADER.len() + 1); + + buffer.extend_from_slice(HEADER); + rand::distributions::Alphanumeric + .sample_iter(rand::thread_rng()) + .take(cnt) + .for_each(|c| buffer.push(c)); + buffer.push(b'\n'); + + stream.write_all(&buffer).await?; + let _ = stream.shutdown().await; + + // read until eof + let mut buf = Vec::with_capacity(1024); + let r = stream.read_to_end(&mut buf).await; + r.map(|_| ()) +} diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs new file mode 100644 index 00000000..8f0382dd --- /dev/null +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs @@ -0,0 +1,6 @@ +mod connector; +mod stream; +mod utils; + +use utils::prelude; +pub use connector::Connector; \ No newline at end of file diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs new file mode 100644 index 00000000..5eef17dc --- /dev/null +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs @@ -0,0 +1,508 @@ +use std::{ + mem::MaybeUninit, + pin::Pin, + ptr::{copy, copy_nonoverlapping}, + task::{ready, Poll}, +}; + +use byteorder::{BigEndian, WriteBytesExt}; +use bytes::{BufMut, BytesMut}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +use super::utils::{prelude::*, Hmac, *}; + +#[derive(Default, Debug)] +pub enum ReadState { + #[default] + WaitingHeader, + WaitingData(usize, [u8; TLS_HEADER_SIZE]), + FlushingData, +} + +#[derive(Default, Debug)] +pub enum WriteState { + #[default] + BuildingData, + FlushingData(usize, usize, usize), +} + +pub trait AsyncReadUnpin: AsyncRead + Unpin {} + +impl AsyncReadUnpin for T {} + +pub trait ReadExtBase { + fn prepare(&mut self) -> (&mut dyn AsyncReadUnpin, &mut BytesMut, &mut usize); +} + +pub trait ReadExt { + fn poll_read_exact( + &mut self, + cx: &mut std::task::Context, + size: usize, + ) -> Poll>; +} + +impl ReadExt for T { + fn poll_read_exact( + &mut self, + cx: &mut std::task::Context, + size: usize, + ) -> Poll> { + let (raw, read_buf, read_pos) = self.prepare(); + read_buf.reserve(size); + // # safety: read_buf has reserved `size` + unsafe { read_buf.set_len(size) } + loop { + if *read_pos < size { + // # safety: read_pos]) + }; + let mut buf = ReadBuf::uninit(dst); + let ptr = buf.filled().as_ptr(); + ready!(Pin::new(&mut *raw).poll_read(cx, &mut buf))?; + assert_eq!(ptr, buf.filled().as_ptr()); + if buf.filled().is_empty() { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "unexpected eof", + ))); + } + *read_pos += buf.filled().len(); + } else { + assert!(*read_pos == size); + *read_pos = 0; + return Poll::Ready(Ok(())); + } + } + } +} + +#[derive(Clone, Debug)] +pub struct Certs { + pub(crate) server_random: [u8; TLS_RANDOM_SIZE], + pub(crate) hmac: Hmac, + pub(crate) key: Vec, +} + +pub struct ProxyTlsStream { + pub raw: S, + password: String, + + pub read_state: ReadState, + pub read_pos: usize, + pub read_buf: BytesMut, + + // need to get from the handshake packets + pub certs: Option, + read_authorized: bool, + tls13: bool, + + // if true, the stream will only act as a wrapper, and won't modify the inner byte stream + pub fake_request: bool, +} + +impl ProxyTlsStream { + pub fn new(raw: S, password: &str) -> Self { + Self { + raw, + password: password.to_string(), + + read_state: Default::default(), + read_pos: Default::default(), + read_buf: Default::default(), + + certs: None, + read_authorized: false, + tls13: false, + + fake_request: false, + } + } + + #[inline(always)] + pub fn authorized(&self) -> bool { + self.read_authorized + } + + #[inline(always)] + pub fn state(&self) -> &Option { + &self.certs + } + + #[allow(unused)] + pub fn tls13(&self) -> bool { + self.tls13 + } +} + +impl ReadExtBase for ProxyTlsStream { + fn prepare(&mut self) -> (&mut dyn AsyncReadUnpin, &mut BytesMut, &mut usize) { + (&mut self.raw, &mut self.read_buf, &mut self.read_pos) + } +} + +impl AsyncRead for ProxyTlsStream { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + let this = self.get_mut(); + + if this.fake_request { + return Pin::new(&mut this.raw).poll_read(cx, buf); + } + + // let r = Pin::new(&mut this.raw).poll_read(cx, buf); + + loop { + match this.read_state { + ReadState::WaitingHeader => { + ready!(this.poll_read_exact(cx, TLS_HEADER_SIZE))?; + let buf = this.read_buf.split().freeze().to_vec(); + + let mut size: [u8; 2] = Default::default(); + size.copy_from_slice(&buf[3..5]); + let data_size = u16::from_be_bytes(size) as usize; + + let mut header = [0u8; TLS_HEADER_SIZE]; + header.copy_from_slice(&buf[..TLS_HEADER_SIZE]); + + this.read_state = ReadState::WaitingData(data_size, header); + } + ReadState::WaitingData(size, mut header) => { + ready!(this.poll_read_exact(cx, size))?; + // now the data is ready with the required size + let mut body = this.read_buf.split().freeze().to_vec(); + + match header[0] { + HANDSHAKE => { + if body.len() > SERVER_RANDOM_OFFSET + TLS_RANDOM_SIZE + && body[0] == SERVER_HELLO + { + let mut server_random = [0; TLS_RANDOM_SIZE]; + unsafe { + copy_nonoverlapping( + body.as_ptr().add(SERVER_RANDOM_OFFSET), + server_random.as_mut_ptr(), + TLS_RANDOM_SIZE, + ) + } + let hmac = Hmac::new(&this.password, (&server_random, &[])); + let key = kdf(&this.password, &server_random); + this.certs = Some(Certs { + server_random, + hmac, + key, + }); + this.tls13 = support_tls13(&body); + } + } + APPLICATION_DATA => { + this.read_authorized = false; + if body.len() > HMAC_SIZE { + if let Some(Certs { hmac, key, .. }) = this.certs.as_mut() { + hmac.update(&body[HMAC_SIZE..]); + if hmac.finalize() == body[0..HMAC_SIZE] { + // 1. xor to the the original data + xor_slice(&mut body[HMAC_SIZE..], key); + // 2. remove the hmac + unsafe { + copy( + body.as_ptr().add(HMAC_SIZE), + body.as_mut_ptr(), + body.len() - HMAC_SIZE, + ) + }; + // 3. rewrite the data size in the header + (&mut header[3..5]) + .write_u16::(size as u16 - HMAC_SIZE as u16) + .unwrap(); + this.read_authorized = true; + // 4. rewrite the body length to be put into the read buf + unsafe { + body.set_len(body.len() - HMAC_SIZE); + } + // 4. put the header and body into our own read buf + tracing::debug!("shadowtls authorization sucess"); + } else { + tracing::debug!("shadowtls verification failed"); + } + } + } + } + _ => {} + } + + this.read_buf.put(&header[..]); + this.read_buf.put(&body[..]); + this.read_state = ReadState::FlushingData; + } + ReadState::FlushingData => { + // now the data is ready in the read_buf + let size = this.read_buf.len(); + let to_read = std::cmp::min(buf.remaining(), size); + let payload = this.read_buf.split_to(to_read); + buf.put_slice(&payload); + if to_read < size { + // there're unread data, continues in next poll + this.read_state = ReadState::FlushingData; + } else { + // all data consumed, ready to read next chunk + this.read_state = ReadState::WaitingHeader; + } + + return Poll::Ready(Ok(())); + } + } + } + } +} + +impl AsyncWrite for ProxyTlsStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + let this = self.get_mut(); + Pin::new(&mut this.raw).poll_write(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let this = self.get_mut(); + Pin::new(&mut this.raw).poll_flush(cx) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let this = self.get_mut(); + Pin::new(&mut this.raw).poll_shutdown(cx) + } +} + +#[derive(Debug)] +pub struct VerifiedStream { + pub raw: S, + client_cert: Hmac, + server_cert: Hmac, + nop_cert: Option, + + pub read_buf: BytesMut, + pub read_pos: usize, + pub read_state: ReadState, + + pub write_buf: BytesMut, + pub write_state: WriteState, +} + +impl VerifiedStream { + pub(crate) fn new( + raw: S, + client_cert: Hmac, + server_cert: Hmac, + nop_cert: Option, + ) -> Self { + Self { + raw, + client_cert, + server_cert, + nop_cert, + read_buf: Default::default(), + read_pos: Default::default(), + read_state: Default::default(), + write_buf: Default::default(), + write_state: Default::default(), + } + } +} + +impl ReadExtBase for VerifiedStream { + fn prepare(&mut self) -> (&mut dyn AsyncReadUnpin, &mut BytesMut, &mut usize) { + (&mut self.raw, &mut self.read_buf, &mut self.read_pos) + } +} + +impl AsyncRead for VerifiedStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + let this = self.get_mut(); + + loop { + match this.read_state { + ReadState::WaitingHeader => { + ready!(this.poll_read_exact(cx, TLS_HEADER_SIZE))?; + let buf = this.read_buf.split().freeze().to_vec(); + + let mut size: [u8; 2] = Default::default(); + size.copy_from_slice(&buf[3..5]); + let data_size = u16::from_be_bytes(size) as usize; + + let mut header = [0u8; TLS_HEADER_SIZE]; + header.copy_from_slice(&buf[..TLS_HEADER_SIZE]); + + this.read_state = ReadState::WaitingData(data_size, header); + } + ReadState::WaitingData(size, header) => { + ready!(this.poll_read_exact(cx, size))?; + // now the data is ready with the required size + let mut data = this.read_buf.split().freeze().to_vec(); + + match header[0] { + APPLICATION_DATA => { + // ignore the rest useless data + if let Some(ref mut nop_cert) = this.nop_cert { + if verify_appdata(&header, &mut data, nop_cert, false) { + this.read_state = ReadState::WaitingHeader; + continue; + } else { + this.nop_cert.take(); + } + } + + // the application data from the data server + // we need to verfiy and removec the hmac(4 bytes) + if verify_appdata(&header, &mut data, &mut this.server_cert, true) { + // modify data, reuse the read buf + tracing::trace!("shadowtls verify appdata success, strip the hmac"); + this.read_buf.clear(); + this.read_buf.put(&data[HMAC_SIZE..]); + this.read_state = ReadState::FlushingData; + } else { + tracing::error!("shadowtls appdata verify failed"); + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "appdata verify failed", + ))); + } + } + _ => {} + } + } + ReadState::FlushingData => { + // now the data is ready in the read_buf + let size = this.read_buf.len(); + let to_read = std::cmp::min(buf.remaining(), size); + let payload = this.read_buf.split_to(to_read); + buf.put_slice(&payload); + if to_read < size { + // there're unread data, continues in next poll + this.read_state = ReadState::FlushingData; + } else { + // all data consumed, ready to read next chunk + this.read_state = ReadState::WaitingHeader; + } + + return Poll::Ready(Ok(())); + } + } + } + } +} + +impl AsyncWrite for VerifiedStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + let this = self.get_mut(); + loop { + match this.write_state { + WriteState::BuildingData => { + // header(5 bytes) + zero hmac(4 bytes) + const DEFAULT_HEADER_HMAC: [u8; TLS_HMAC_HEADER_SIZE] = + [APPLICATION_DATA, TLS_MAJOR, TLS_MINOR.0, 0, 0, 0, 0, 0, 0]; + + let mut header_body = Vec::with_capacity(COPY_BUF_SIZE); + header_body.extend_from_slice(&DEFAULT_HEADER_HMAC); + + (&mut header_body[3..5]) + .write_u16::((buf.len() + HMAC_SIZE) as u16) + .unwrap(); + header_body.extend_from_slice(buf); + + this.client_cert.update(buf); + let hmac_val = this.client_cert.finalize(); + this.client_cert.update(&hmac_val); + unsafe { + copy_nonoverlapping( + hmac_val.as_ptr(), + header_body.as_mut_ptr().add(TLS_HEADER_SIZE), + HMAC_SIZE, + ) + }; + + this.write_buf.put_slice(&header_body); + this.write_state = + WriteState::FlushingData(buf.len(), header_body.len(), 0); + } + WriteState::FlushingData(consume, total, written) => { + let nw = ready!(tokio_util::io::poll_write_buf( + Pin::new(&mut this.raw), + cx, + &mut this.write_buf + ))?; + if nw == 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::WriteZero, + "failed to write whole data", + )) + .into(); + } + + if written + nw >= total { + debug_assert_eq!(written + nw, total); + // data chunk written, go to next chunk + this.write_state = WriteState::BuildingData; + return Poll::Ready(Ok(consume)); + } + + this.write_state = WriteState::FlushingData(consume, total, written + nw); + } + } + } + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let this = self.get_mut(); + Pin::new(&mut this.raw).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let this = self.get_mut(); + Pin::new(&mut this.raw).poll_shutdown(cx) + } +} + +fn verify_appdata( + header: &[u8; TLS_HEADER_SIZE], + data: &mut [u8], + hmac: &mut Hmac, + sep: bool, +) -> bool { + if header[1] != TLS_MAJOR || header[2] != TLS_MINOR.0 { + return false; + } + hmac.update(&data[HMAC_SIZE..]); + let hmac_real = hmac.finalize(); + if sep { + hmac.update(&hmac_real); + } + data[0..HMAC_SIZE] == hmac_real +} diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs new file mode 100644 index 00000000..208f289a --- /dev/null +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs @@ -0,0 +1,166 @@ +use std::{io::Read, ptr::copy_nonoverlapping}; + +use byteorder::{BigEndian, ReadBytesExt}; +use hmac::Mac; + +use prelude::*; +use sha2::{Digest, Sha256}; + +pub(crate) mod prelude { + pub(crate) const TLS_MAJOR: u8 = 0x03; + pub(crate) const TLS_MINOR: (u8, u8) = (0x03, 0x01); + pub(crate) const SUPPORTED_VERSIONS_TYPE: u16 = 43; + pub(crate) const TLS_RANDOM_SIZE: usize = 32; + pub(crate) const TLS_HEADER_SIZE: usize = 5; + pub(crate) const TLS_SESSION_ID_SIZE: usize = 32; + pub(crate) const TLS_13: u16 = 0x0304; + + pub(crate) const SERVER_HELLO: u8 = 0x02; + pub(crate) const HANDSHAKE: u8 = 0x16; + pub(crate) const APPLICATION_DATA: u8 = 0x17; + + pub(crate) const SERVER_RANDOM_OFFSET: usize = 1 + 3 + 2; + pub(crate) const SESSION_ID_LEN_IDX: usize = TLS_HEADER_SIZE + 1 + 3 + 2 + TLS_RANDOM_SIZE; + pub(crate) const TLS_HMAC_HEADER_SIZE: usize = TLS_HEADER_SIZE + HMAC_SIZE; + + pub(crate) const COPY_BUF_SIZE: usize = 4096; + pub(crate) const HMAC_SIZE: usize = 4; +} + +#[derive(Clone)] +pub(crate) struct Hmac(hmac::Hmac); + +impl std::fmt::Debug for Hmac { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Hmac").finish() + } +} + +impl Hmac { + #[inline] + pub(crate) fn new(password: &str, init_data: (&[u8], &[u8])) -> Self { + // Note: infact new_from_slice never returns Err. + let mut hmac: hmac::Hmac = + hmac::Hmac::new_from_slice(password.as_bytes()).expect("unable to build hmac instance"); + hmac.update(init_data.0); + hmac.update(init_data.1); + Self(hmac) + } + + #[inline] + // #[tracing::instrument(skip(data), level = "debug")] + pub(crate) fn update(&mut self, data: &[u8]) { + self.0.update(data); + } + + #[inline] + pub(crate) fn finalize(&self) -> [u8; HMAC_SIZE] { + let hmac = self.0.clone(); + let hash = hmac.finalize().into_bytes(); + let mut res = [0; HMAC_SIZE]; + unsafe { copy_nonoverlapping(hash.as_slice().as_ptr(), res.as_mut_ptr(), HMAC_SIZE) }; + res + } + + #[inline] + pub(crate) fn to_owned(&self) -> Self { + Self(self.0.clone()) + } +} + +#[inline] +pub(crate) fn kdf(password: &str, server_random: &[u8]) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(password.as_bytes()); + hasher.update(server_random); + let hash = hasher.finalize(); + hash.to_vec() +} + +#[inline] +pub(crate) fn xor_slice(data: &mut [u8], key: &[u8]) { + data.iter_mut() + .zip(key.iter().cycle()) + .for_each(|(d, k)| *d ^= k); +} + +pub(crate) trait CursorExt { + fn read_by_u16(&mut self) -> std::io::Result>; + fn skip(&mut self, n: usize) -> std::io::Result<()>; + fn skip_by_u8(&mut self) -> std::io::Result; + fn skip_by_u16(&mut self) -> std::io::Result; +} + +impl CursorExt for std::io::Cursor +where + std::io::Cursor: std::io::Read, +{ + #[inline] + fn read_by_u16(&mut self) -> std::io::Result> { + let len = self.read_u16::()?; + let mut buf = vec![0; len as usize]; + self.read_exact(&mut buf)?; + Ok(buf) + } + + #[inline] + fn skip(&mut self, n: usize) -> std::io::Result<()> { + for _ in 0..n { + self.read_u8()?; + } + Ok(()) + } + + #[inline] + fn skip_by_u8(&mut self) -> std::io::Result { + let len = self.read_u8()?; + self.skip(len as usize)?; + Ok(len) + } + + #[inline] + fn skip_by_u16(&mut self) -> std::io::Result { + let len = self.read_u16::()?; + self.skip(len as usize)?; + Ok(len) + } +} + +/// Parse ServerHello and return if tls1.3 is supported. +pub(crate) fn support_tls13(frame: &[u8]) -> bool { + if frame.len() < SESSION_ID_LEN_IDX { + return false; + } + let mut cursor = std::io::Cursor::new(&frame[SESSION_ID_LEN_IDX..]); + macro_rules! read_ok { + ($res: expr) => { + match $res { + Ok(r) => r, + Err(_) => { + return false; + } + } + }; + } + + // skip session id + read_ok!(cursor.skip_by_u8()); + // skip cipher suites + read_ok!(cursor.skip(3)); + // skip ext length + let cnt = read_ok!(cursor.read_u16::()); + + for _ in 0..cnt { + let ext_type = read_ok!(cursor.read_u16::()); + if ext_type != SUPPORTED_VERSIONS_TYPE { + read_ok!(cursor.skip_by_u16()); + continue; + } + let ext_len = read_ok!(cursor.read_u16::()); + let ext_val = read_ok!(cursor.read_u16::()); + let use_tls13 = ext_len == 2 && ext_val == TLS_13; + tracing::debug!("found supported_versions extension, tls1.3: {use_tls13}"); + return use_tls13; + } + false +} diff --git a/clash_lib/src/proxy/utils/socket_helpers.rs b/clash_lib/src/proxy/utils/socket_helpers.rs index fe9ef4b8..1f163db5 100644 --- a/clash_lib/src/proxy/utils/socket_helpers.rs +++ b/clash_lib/src/proxy/utils/socket_helpers.rs @@ -82,6 +82,7 @@ pub async fn new_tcp_stream<'a>( io::ErrorKind::Other, format!("can't resolve dns: {}", address), ))?; + tracing::debug!("resolved addr of {:?}:{:?}", address, dial_addr); let socket = match (dial_addr, resolver.ipv6()) { (IpAddr::V4(_), _) => { From f3b15b07bf685a9452c0f645862ab430da8742f5 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Wed, 3 Apr 2024 22:50:22 +0800 Subject: [PATCH 2/8] fix: remove unnecessary logs; check fake_request's result before return error --- .../src/proxy/shadowsocks/shadow_tls/connector.rs | 13 ++++--------- .../src/proxy/shadowsocks/shadow_tls/stream.rs | 2 -- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs index 86918f90..644425be 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs @@ -48,8 +48,6 @@ impl Connector { pub async fn wrap(opts: &ShadowTlsOption, stream: AnyStream) -> std::io::Result { let proxy_stream = ProxyTlsStream::new(stream, &opts.password); - tracing::trace!("tcp connected, start handshaking"); - let hamc_handshake = Hmac::new(&opts.password, (&[], &[])); let sni_name = rustls::ServerName::try_from(opts.host.as_str()) .unwrap_or_else(|_| panic!("invalid server name: {}", opts.host)); @@ -58,9 +56,6 @@ impl Connector { let mut tls = connector .connect_with(sni_name, proxy_stream, Some(session_id_generator), |_| {}) .await?; - // perform a fake request, will do the handshake - tracing::trace!("handshake done"); - let authorized = tls.get_mut().0.authorized(); let maybe_server_random_and_hamc = tls .get_mut() @@ -69,14 +64,14 @@ impl Connector { .as_ref() .map(|s| (s.server_random, s.hmac.to_owned())); + // whatever the fake_request is successful or not, we should return an error when strict mode is enabled if (!authorized || maybe_server_random_and_hamc.is_none()) && opts.strict { - tracing::warn!("V3 strict enabled: traffic hijacked or TLS1.3 is not supported, perform fake request"); + tracing::warn!("shadow-tls V3 strict enabled: traffic hijacked or TLS1.3 is not supported, perform fake request"); tls.get_mut().0.fake_request = true; + fake_request(tls).await?; - let r = fake_request(tls).await; - - return Err(io::Error::new(io::ErrorKind::Other, format!("V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request, res:{:?}", r))); + return Err(io::Error::new(io::ErrorKind::Other, format!("V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request"))); } let (server_random, hmac_nop) = match maybe_server_random_and_hamc { diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs index 5eef17dc..d5ee8df2 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs @@ -225,7 +225,6 @@ impl AsyncRead for ProxyTlsStream { body.set_len(body.len() - HMAC_SIZE); } // 4. put the header and body into our own read buf - tracing::debug!("shadowtls authorization sucess"); } else { tracing::debug!("shadowtls verification failed"); } @@ -373,7 +372,6 @@ impl AsyncRead for VerifiedStream { // we need to verfiy and removec the hmac(4 bytes) if verify_appdata(&header, &mut data, &mut this.server_cert, true) { // modify data, reuse the read buf - tracing::trace!("shadowtls verify appdata success, strip the hmac"); this.read_buf.clear(); this.read_buf.put(&data[HMAC_SIZE..]); this.read_state = ReadState::FlushingData; From 1b46a412c8dc583406fb828e1d2607acfb27e05c Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Wed, 3 Apr 2024 22:59:22 +0800 Subject: [PATCH 3/8] cargo fmt --- clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs | 7 ++++++- clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs | 2 +- clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs | 3 +-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs index 644425be..ab2ef5ea 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs @@ -71,7 +71,12 @@ impl Connector { tls.get_mut().0.fake_request = true; fake_request(tls).await?; - return Err(io::Error::new(io::ErrorKind::Other, format!("V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request"))); + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request" + ), + )); } let (server_random, hmac_nop) = match maybe_server_random_and_hamc { diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs index 8f0382dd..d5285a14 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/mod.rs @@ -2,5 +2,5 @@ mod connector; mod stream; mod utils; +pub use connector::Connector; use utils::prelude; -pub use connector::Connector; \ No newline at end of file diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs index d5ee8df2..c630e3f2 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs @@ -441,8 +441,7 @@ impl AsyncWrite for VerifiedStream { }; this.write_buf.put_slice(&header_body); - this.write_state = - WriteState::FlushingData(buf.len(), header_body.len(), 0); + this.write_state = WriteState::FlushingData(buf.len(), header_body.len(), 0); } WriteState::FlushingData(consume, total, written) => { let nw = ready!(tokio_util::io::poll_write_buf( From 96b93f30bae962553950a4169f0aecc31c1871ed Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Thu, 4 Apr 2024 00:37:56 +0800 Subject: [PATCH 4/8] add MultiDockerTestRunner to support shadowtls plugin test; modify some names --- clash_lib/src/proxy/shadowsocks/mod.rs | 69 ++++++++++- .../src/proxy/utils/test_utils/consts.rs | 1 + .../proxy/utils/test_utils/docker_runner.rs | 108 +++++++++++++++--- clash_lib/src/proxy/utils/test_utils/mod.rs | 15 ++- 4 files changed, 170 insertions(+), 23 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 64dccd91..c3530ce4 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -330,7 +330,10 @@ impl OutboundHandler for Handler { #[cfg(all(test, not(ci)))] mod tests { - use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; + use crate::proxy::utils::test_utils::docker_runner::{ + MultiDockerTestRunner, DockerTestRunnerBuilder, + }; + use crate::proxy::utils::test_utils::run_chained; use super::super::utils::test_utils::{consts::*, docker_runner::DockerTestRunner, run}; @@ -338,12 +341,14 @@ mod tests { const PASSWORD: &str = "FzcLbKs2dY9mhL"; const CIPHER: &str = "aes-256-gcm"; + const SHADOW_TLS_PASSWORD: &str = "password"; - async fn get_runner() -> anyhow::Result { + async fn get_ss_runner(port: u16) -> anyhow::Result { + let host = format!("0.0.0.0:{}", port); DockerTestRunnerBuilder::new() .image(IMAGE_SS_RUST) .entrypoint(&["ssserver"]) - .cmd(&["-s", "0.0.0.0:10002", "-m", CIPHER, "-k", PASSWORD, "-U"]) + .cmd(&["-s", &host, "-m", CIPHER, "-k", PASSWORD, "-U"]) .build() .await } @@ -361,7 +366,63 @@ mod tests { plugin_opts: Default::default(), udp: false, }; + let port = opts.port; let handler = Handler::new(opts); - run(handler, get_runner()).await + run(handler, get_ss_runner(port)).await + } + + async fn get_shadowtls_runner( + ss_port: u16, + stls_port: u16, + ) -> anyhow::Result { + let ss_server_env = format!("SERVER=127.0.0.1:{}", ss_port); + let listen_env = format!("LISTEN=0.0.0.0:{}", stls_port); + let password = format!("PASSWORD={}", SHADOW_TLS_PASSWORD); + DockerTestRunnerBuilder::new() + .image(IMAGE_SHADOW_TLS) + .env(&[ + "MODE=server", + // the port that we need to fill in the config + &listen_env, + // shadowsocks server addr + &ss_server_env, + "TLS=www.feishu.cn:443", + &password, + "V3=1", + ]) + // .cmd(&["-s", "0.0.0.0:10002", "-m", CIPHER, "-k", PASSWORD, "-U"]) + .build() + .await + } + + #[tokio::test] + #[serial_test::serial] + async fn test_shadowtls() -> anyhow::Result<()> { + // the real port that used for communication + let shadow_tls_port = 10002; + // not important, you can assign any port that is not conflict with others + let ss_port = 10004; + let opts = HandlerOptions { + name: "test-ss".to_owned(), + common_opts: Default::default(), + server: LOCAL_ADDR.to_owned(), + port: shadow_tls_port, + password: PASSWORD.to_owned(), + cipher: CIPHER.to_owned(), + plugin_opts: Some(OBFSOption::ShadowTls(ShadowTlsOption { + host: "www.feishu.cn".to_owned(), + password: "password".to_owned(), + strict: true, + })), + udp: false, + }; + let handler = Handler::new(opts); + // we need to store all the runners in a container, to make sure all of them can be destroyed after the test + let mut chained = MultiDockerTestRunner::default(); + chained.add(get_ss_runner(ss_port)).await; + chained + .add(get_shadowtls_runner(ss_port, shadow_tls_port)) + .await; + run_chained(handler, chained).await } } diff --git a/clash_lib/src/proxy/utils/test_utils/consts.rs b/clash_lib/src/proxy/utils/test_utils/consts.rs index 600c9221..58ef4812 100644 --- a/clash_lib/src/proxy/utils/test_utils/consts.rs +++ b/clash_lib/src/proxy/utils/test_utils/consts.rs @@ -3,6 +3,7 @@ pub const EXAMPLE_REQ: &[u8] = b"GET / HTTP/1.1\r\nHost: example.com\r\nAccept: pub const EXAMLE_RESP_200: &[u8] = b"HTTP/1.1 200"; pub const IMAGE_SS_RUST: &str = "ghcr.io/shadowsocks/ssserver-rust:latest"; +pub const IMAGE_SHADOW_TLS: &str = "ghcr.io/ihciah/shadow-tls:latest"; pub const IMAGE_TROJAN_GO: &str = "p4gefau1t/trojan-go:latest"; pub const IMAGE_VMESS: &str = "v2fly/v2fly-core:v4.45.2"; pub const IMAGE_XRAY: &str = "teddysun/xray:latest"; diff --git a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs index ac12ffc6..98997cb7 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -17,7 +17,7 @@ pub struct DockerTestRunner { } impl DockerTestRunner { - pub async fn new<'a>( + pub async fn try_new<'a>( image_conf: Option>, container_conf: Config, ) -> Result { @@ -39,10 +39,61 @@ impl DockerTestRunner { }) } - // will make sure the container is cleaned up after the future is finished - pub async fn run_and_cleanup( + // you can run the cleanup manually + pub async fn cleanup(self) -> anyhow::Result<()> { + let s = self + .instance + .remove_container( + &self.id, + Some(RemoveContainerOptions { + force: true, + ..Default::default() + }), + ) + .await?; + Ok(s) + } +} + +#[derive(Default)] +pub struct MultiDockerTestRunner { + runners: Vec, +} + +impl MultiDockerTestRunner { + #[allow(unused)] + pub fn new(runners: Vec) -> Self { + Self { runners } + } + + pub async fn add(&mut self, creator: impl Future>) { + match creator.await { + Ok(runner) => { + self.runners.push(runner); + } + Err(e) => { + tracing::warn!( + "cannot start container, please check the docker environment, error: {:?}", + e + ); + } + } + } +} + +#[async_trait::async_trait] +pub trait DockerTest { + async fn run_and_cleanup( + self, + f: impl Future> + Send + 'static, + ) -> anyhow::Result<()>; +} + +#[async_trait::async_trait] +impl DockerTest for DockerTestRunner { + async fn run_and_cleanup( self, - f: impl Future>, + f: impl Future> + Send + 'static, ) -> anyhow::Result<()> { let fut = Box::pin(f); // let res = fut.await; @@ -61,20 +112,33 @@ impl DockerTestRunner { res } +} - // you can run the cleanup manually - pub async fn cleanup(self) -> anyhow::Result<()> { - let s = self - .instance - .remove_container( - &self.id, - Some(RemoveContainerOptions { - force: true, - ..Default::default() - }), - ) - .await?; - Ok(s) +#[async_trait::async_trait] +impl DockerTest for MultiDockerTestRunner { + async fn run_and_cleanup( + self, + f: impl Future> + Send + 'static, + ) -> anyhow::Result<()> { + let fut = Box::pin(f); + // let res = fut.await; + // make sure the container is cleaned up + let res = tokio::select! { + res = fut => { + res + }, + _ = tokio::time::sleep(std::time::Duration::from_secs(TIMEOUT_DURATION))=> { + tracing::warn!("timeout"); + Err(anyhow::anyhow!("timeout")) + } + }; + + // cleanup all the docker containers + for runner in self.runners { + runner.cleanup().await?; + } + + res } } @@ -89,6 +153,7 @@ pub struct DockerTestRunnerBuilder { host_config: HostConfig, exposed_ports: Vec, cmd: Option>, + env: Option>, entrypoint: Option>, _server_port: u16, } @@ -103,6 +168,7 @@ impl Default for DockerTestRunnerBuilder { .map(|x| x.to_string()) .collect::>(), cmd: None, + env: None, entrypoint: None, _server_port: PORT, } @@ -135,6 +201,11 @@ impl DockerTestRunnerBuilder { self } + pub fn env(mut self, env: &[&str]) -> Self { + self.env = Some(env.iter().map(|x| x.to_string()).collect()); + self + } + pub fn entrypoint(mut self, entrypoint: &[&str]) -> Self { self.entrypoint = Some(entrypoint.iter().map(|x| x.to_string()).collect()); self @@ -165,7 +236,7 @@ impl DockerTestRunnerBuilder { .map(|x| (x, Default::default())) .collect::>(); - DockerTestRunner::new( + DockerTestRunner::try_new( Some(CreateImageOptions { from_image: self.image.clone(), ..Default::default() @@ -175,6 +246,7 @@ impl DockerTestRunnerBuilder { tty: Some(true), entrypoint: self.entrypoint, cmd: self.cmd, + env: self.env, exposed_ports: Some(exposed), host_config: Some(self.host_config), ..Default::default() diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index d61905bb..c85dc26b 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -15,7 +15,7 @@ use tokio::{ }; use tracing::info; -use self::docker_runner::DockerTestRunner; +use self::docker_runner::{MultiDockerTestRunner, DockerTest, DockerTestRunner}; pub mod config_helper; pub mod consts; @@ -187,7 +187,20 @@ pub async fn run( return Err(e); } }; + run_inner(handler, watch).await +} +pub async fn run_chained( + handler: Arc, + chained: MultiDockerTestRunner, +) -> anyhow::Result<()> { + run_inner(handler, chained).await +} + +pub async fn run_inner( + handler: Arc, + watch: impl DockerTest, +) -> anyhow::Result<()> { watch .run_and_cleanup(async move { let rv = ping_pong_test(handler.clone(), 10001).await; From fd59d901f2cd163ebd3695ff9a7a69853c7abeb1 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Thu, 4 Apr 2024 00:43:05 +0800 Subject: [PATCH 5/8] simplify docker test workflow --- clash_lib/src/proxy/shadowsocks/mod.rs | 10 +++---- clash_lib/src/proxy/trojan/mod.rs | 4 +-- clash_lib/src/proxy/utils/test_utils/mod.rs | 30 +++------------------ clash_lib/src/proxy/vmess/mod.rs | 6 ++--- 4 files changed, 12 insertions(+), 38 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index c3530ce4..ae83621a 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -330,12 +330,10 @@ impl OutboundHandler for Handler { #[cfg(all(test, not(ci)))] mod tests { + use super::super::utils::test_utils::{consts::*, docker_runner::DockerTestRunner, run}; use crate::proxy::utils::test_utils::docker_runner::{ - MultiDockerTestRunner, DockerTestRunnerBuilder, + DockerTestRunnerBuilder, MultiDockerTestRunner, }; - use crate::proxy::utils::test_utils::run_chained; - - use super::super::utils::test_utils::{consts::*, docker_runner::DockerTestRunner, run}; use super::*; @@ -368,7 +366,7 @@ mod tests { }; let port = opts.port; let handler = Handler::new(opts); - run(handler, get_ss_runner(port)).await + run(handler, get_ss_runner(port).await?).await } async fn get_shadowtls_runner( @@ -423,6 +421,6 @@ mod tests { chained .add(get_shadowtls_runner(ss_port, shadow_tls_port)) .await; - run_chained(handler, chained).await + run(handler, chained).await } } diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 233b8b92..2cf224d3 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -278,7 +278,7 @@ mod tests { })), }; let handler = Handler::new(opts); - run(handler, get_ws_runner()).await + run(handler, get_ws_runner().await?).await } async fn get_grpc_runner() -> anyhow::Result { @@ -317,6 +317,6 @@ mod tests { })), }; let handler = Handler::new(opts); - run(handler, get_grpc_runner()).await + run(handler, get_grpc_runner().await?).await } } diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index c85dc26b..af1b65db 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -8,14 +8,14 @@ use crate::{ proxy::OutboundHandler, session::{Session, SocksAddr}, }; -use futures::{future::select_all, Future}; +use futures::future::select_all; use tokio::{ io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::TcpListener, }; use tracing::info; -use self::docker_runner::{MultiDockerTestRunner, DockerTest, DockerTestRunner}; +use self::docker_runner::DockerTest; pub mod config_helper; pub mod consts; @@ -176,31 +176,7 @@ pub async fn latency_test( Ok(end_time.duration_since(start_time)) } -pub async fn run( - handler: Arc, - runner_creater: impl Future>, -) -> anyhow::Result<()> { - let watch = match runner_creater.await { - Ok(runner) => runner, - Err(e) => { - tracing::warn!("cannot start container, please check the docker environment"); - return Err(e); - } - }; - run_inner(handler, watch).await -} - -pub async fn run_chained( - handler: Arc, - chained: MultiDockerTestRunner, -) -> anyhow::Result<()> { - run_inner(handler, chained).await -} - -pub async fn run_inner( - handler: Arc, - watch: impl DockerTest, -) -> anyhow::Result<()> { +pub async fn run(handler: Arc, watch: impl DockerTest) -> anyhow::Result<()> { watch .run_and_cleanup(async move { let rv = ping_pong_test(handler.clone(), 10001).await; diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index c600a0ef..18c3ba5f 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -303,7 +303,7 @@ mod tests { })), }; let handler = Handler::new(opts); - run(handler, get_ws_runner()).await + run(handler, get_ws_runner().await?).await } async fn get_grpc_runner() -> anyhow::Result { @@ -346,7 +346,7 @@ mod tests { })), }; let handler = Handler::new(opts); - run(handler, get_grpc_runner()).await + run(handler, get_grpc_runner().await?).await } async fn get_h2_runner() -> anyhow::Result { @@ -389,6 +389,6 @@ mod tests { })), }; let handler = Handler::new(opts); - run(handler, get_h2_runner()).await + run(handler, get_h2_runner().await?).await } } From 5172b18012966405b7f42de0152397eea0852ea2 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Fri, 5 Apr 2024 16:12:01 +0800 Subject: [PATCH 6/8] fix clippy warnings --- clash_lib/src/proxy/shadowsocks/mod.rs | 4 +- .../proxy/shadowsocks/shadow_tls/connector.rs | 12 ++--- .../proxy/shadowsocks/shadow_tls/stream.rs | 47 +++++++++---------- .../proxy/utils/test_utils/config_helper.rs | 2 +- .../proxy/utils/test_utils/docker_runner.rs | 11 ++--- clash_lib/src/proxy/utils/test_utils/mod.rs | 6 +-- 6 files changed, 38 insertions(+), 44 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index ae83621a..a6f73cfc 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -263,8 +263,8 @@ impl OutboundHandler for Handler { } OBFSOption::ShadowTls(opts) => { tracing::debug!("using shadow-tls with option: {:?}", opts); - let res = shadow_tls::Connector::wrap(opts, s).await?; - res + + (shadow_tls::Connector::wrap(opts, s).await?) as _ } }, None => s, diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs index ab2ef5ea..2086948e 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs @@ -41,8 +41,8 @@ impl Connector { .with_safe_defaults() .with_root_certificates(GLOBAL_ROOT_STORE.clone()) .with_no_client_auth(); - let connector = TlsConnector::from(Arc::new(tls_config.clone())); - connector + + TlsConnector::from(Arc::new(tls_config.clone())) } pub async fn wrap(opts: &ShadowTlsOption, stream: AnyStream) -> std::io::Result { @@ -73,9 +73,7 @@ impl Connector { return Err(io::Error::new( io::ErrorKind::Other, - format!( - "V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request" - ), + "V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request".to_string(), )); } @@ -84,7 +82,7 @@ impl Connector { None => { return Err(io::Error::new( io::ErrorKind::Other, - format!("server random and hmac not extracted from handshake, fail to connect"), + "server random and hmac not extracted from handshake, fail to connect".to_string(), )); } }; @@ -99,7 +97,7 @@ impl Connector { Some(hmac_nop), ); - return Ok(Box::new(verified_stream)); + Ok(Box::new(verified_stream)) } } diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs index c630e3f2..00bd48e7 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs @@ -356,34 +356,31 @@ impl AsyncRead for VerifiedStream { // now the data is ready with the required size let mut data = this.read_buf.split().freeze().to_vec(); - match header[0] { - APPLICATION_DATA => { - // ignore the rest useless data - if let Some(ref mut nop_cert) = this.nop_cert { - if verify_appdata(&header, &mut data, nop_cert, false) { - this.read_state = ReadState::WaitingHeader; - continue; - } else { - this.nop_cert.take(); - } - } - - // the application data from the data server - // we need to verfiy and removec the hmac(4 bytes) - if verify_appdata(&header, &mut data, &mut this.server_cert, true) { - // modify data, reuse the read buf - this.read_buf.clear(); - this.read_buf.put(&data[HMAC_SIZE..]); - this.read_state = ReadState::FlushingData; + if header[0] == APPLICATION_DATA { + // ignore the rest useless data + if let Some(ref mut nop_cert) = this.nop_cert { + if verify_appdata(&header, &mut data, nop_cert, false) { + this.read_state = ReadState::WaitingHeader; + continue; } else { - tracing::error!("shadowtls appdata verify failed"); - return Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "appdata verify failed", - ))); + this.nop_cert.take(); } } - _ => {} + + // the application data from the data server + // we need to verfiy and removec the hmac(4 bytes) + if verify_appdata(&header, &mut data, &mut this.server_cert, true) { + // modify data, reuse the read buf + this.read_buf.clear(); + this.read_buf.put(&data[HMAC_SIZE..]); + this.read_state = ReadState::FlushingData; + } else { + tracing::error!("shadowtls appdata verify failed"); + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "appdata verify failed", + ))); + } } } ReadState::FlushingData => { diff --git a/clash_lib/src/proxy/utils/test_utils/config_helper.rs b/clash_lib/src/proxy/utils/test_utils/config_helper.rs index e6c34271..86db209b 100644 --- a/clash_lib/src/proxy/utils/test_utils/config_helper.rs +++ b/clash_lib/src/proxy/utils/test_utils/config_helper.rs @@ -44,7 +44,7 @@ pub async fn load_config() -> anyhow::Result<( debug!("initializing cache store"); let cache_store = profile::ThreadSafeCacheFile::new( - PathBuf::from(root) + root .join("cache.db") .as_path() .to_str() diff --git a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs index 98997cb7..7a043ead 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -17,8 +17,8 @@ pub struct DockerTestRunner { } impl DockerTestRunner { - pub async fn try_new<'a>( - image_conf: Option>, + pub async fn try_new( + image_conf: Option>, container_conf: Config, ) -> Result { let docker: Docker = Docker::connect_with_socket_defaults()?; @@ -41,8 +41,7 @@ impl DockerTestRunner { // you can run the cleanup manually pub async fn cleanup(self) -> anyhow::Result<()> { - let s = self - .instance + self.instance .remove_container( &self.id, Some(RemoveContainerOptions { @@ -51,7 +50,7 @@ impl DockerTestRunner { }), ) .await?; - Ok(s) + Ok(()) } } @@ -214,7 +213,7 @@ impl DockerTestRunnerBuilder { pub fn mounts(mut self, pairs: &[(&str, &str)]) -> Self { self.host_config.mounts = Some( pairs - .into_iter() + .iter() .map(|(src, dst)| Mount { target: Some(dst.to_string()), source: Some(src.to_string()), diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index af1b65db..dafe3fc8 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -197,13 +197,13 @@ pub async fn run(handler: Arc, watch: impl DockerTest) -> a }, ) .await; - if rv.is_err() { - return Err(rv.unwrap_err()); + if let Err(e) = rv { + return Err(e); } else { tracing::info!("latency test success: {}", rv.unwrap().as_millis()); } - return Ok(()); + Ok(()) }) .await } From d5f13c5966e0d7dc34b0bd09f224e28f5048511f Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Sat, 6 Apr 2024 20:58:03 +0800 Subject: [PATCH 7/8] cargo fmt --- clash_lib/src/proxy/shadowsocks/mod.rs | 2 +- clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs | 8 +++++--- clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs | 2 +- clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs | 1 + clash_lib/src/proxy/utils/test_utils/config_helper.rs | 6 +----- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index a6f73cfc..b11d4427 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -263,7 +263,7 @@ impl OutboundHandler for Handler { } OBFSOption::ShadowTls(opts) => { tracing::debug!("using shadow-tls with option: {:?}", opts); - + (shadow_tls::Connector::wrap(opts, s).await?) as _ } }, diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs index 2086948e..af0413d1 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/connector.rs @@ -41,7 +41,7 @@ impl Connector { .with_safe_defaults() .with_root_certificates(GLOBAL_ROOT_STORE.clone()) .with_no_client_auth(); - + TlsConnector::from(Arc::new(tls_config.clone())) } @@ -73,7 +73,8 @@ impl Connector { return Err(io::Error::new( io::ErrorKind::Other, - "V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request".to_string(), + "V3 strict enabled: traffic hijacked or TLS1.3 is not supported, fake request" + .to_string(), )); } @@ -82,7 +83,8 @@ impl Connector { None => { return Err(io::Error::new( io::ErrorKind::Other, - "server random and hmac not extracted from handshake, fail to connect".to_string(), + "server random and hmac not extracted from handshake, fail to connect" + .to_string(), )); } }; diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs index 00bd48e7..f01cab93 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/stream.rs @@ -9,7 +9,7 @@ use byteorder::{BigEndian, WriteBytesExt}; use bytes::{BufMut, BytesMut}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use super::utils::{prelude::*, Hmac, *}; +use super::utils::{prelude::*, *}; #[derive(Default, Debug)] pub enum ReadState { diff --git a/clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs b/clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs index 208f289a..70e930d5 100644 --- a/clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs +++ b/clash_lib/src/proxy/shadowsocks/shadow_tls/utils.rs @@ -84,6 +84,7 @@ pub(crate) fn xor_slice(data: &mut [u8], key: &[u8]) { .for_each(|(d, k)| *d ^= k); } +#[allow(unused)] pub(crate) trait CursorExt { fn read_by_u16(&mut self) -> std::io::Result>; fn skip(&mut self, n: usize) -> std::io::Result<()>; diff --git a/clash_lib/src/proxy/utils/test_utils/config_helper.rs b/clash_lib/src/proxy/utils/test_utils/config_helper.rs index 86db209b..05b71311 100644 --- a/clash_lib/src/proxy/utils/test_utils/config_helper.rs +++ b/clash_lib/src/proxy/utils/test_utils/config_helper.rs @@ -44,11 +44,7 @@ pub async fn load_config() -> anyhow::Result<( debug!("initializing cache store"); let cache_store = profile::ThreadSafeCacheFile::new( - root - .join("cache.db") - .as_path() - .to_str() - .unwrap(), + root.join("cache.db").as_path().to_str().unwrap(), config.profile.store_selected, ); From 3d537eccbc06e282450e2ab1b4fb917ae304d068 Mon Sep 17 00:00:00 2001 From: Yuwei B Date: Tue, 9 Apr 2024 10:45:03 +1000 Subject: [PATCH 8/8] f --- Cargo.lock | 1 - clash_lib/Cargo.toml | 1 - clash_lib/src/proxy/trojan/mod.rs | 10 +++++++--- clash_lib/src/proxy/vmess/mod.rs | 11 +++++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87d85059..bdd55299 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1159,7 +1159,6 @@ dependencies = [ "tracing-opentelemetry", "tracing-oslog", "tracing-subscriber", - "tracing-test", "tracing-timing", "tuic", "tuic-quinn", diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index 4591c4d7..faf9e6d7 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -127,7 +127,6 @@ tokio-test = "0.4.4" axum-macros = "0.4.0" bollard = "0.16" serial_test = "3.0.0" -tracing-test = "0.2.4" [target.'cfg(macos)'.dependencies] security-framework = "2.10.0" diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 7b4bfcc8..6a00e172 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -225,8 +225,6 @@ mod tests { use std::collections::HashMap; - use tracing_test::traced_test; - use crate::proxy::utils::test_utils::{ config_helper::test_config_base_dir, consts::*, @@ -254,9 +252,15 @@ mod tests { } #[tokio::test] - #[traced_test] #[serial_test::serial] async fn test_trojan_ws() -> anyhow::Result<()> { + let _ = tracing_subscriber::fmt() + // any additional configuration of the subscriber you might want here.. + .try_init(); + + let span = tracing::info_span!("test_trojan_ws"); + let _enter = span.enter(); + let opts = Opts { name: "test-trojan-ws".to_owned(), common_opts: Default::default(), diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index f8977b5a..178ddc1c 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -255,9 +255,6 @@ impl OutboundHandler for Handler { #[cfg(all(test, not(ci)))] mod tests { - - use tracing_test::traced_test; - use crate::proxy::utils::test_utils::{ config_helper::test_config_base_dir, consts::*, @@ -279,9 +276,15 @@ mod tests { } #[tokio::test] - #[traced_test] #[serial_test::serial] async fn test_vmess_ws() -> anyhow::Result<()> { + let _ = tracing_subscriber::fmt() + // any additional configuration of the subscriber you might want here.. + .try_init(); + + let span = tracing::info_span!("test_vmess_ws"); + let _enter = span.enter(); + let opts = HandlerOptions { name: "test-vmess-ws".into(), common_opts: Default::default(),