From b395058c2b2d925ca20fb070ad49a36d3c472e87 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Sun, 17 Mar 2024 16:49:25 +0800 Subject: [PATCH 01/24] feat: add docker test for current protocol(vmess, ss, trojan) --- Cargo.lock | 89 +++++++ clash/tests/data/config/example.org-key.pem | 28 ++ clash/tests/data/config/example.org.pem | 25 ++ clash/tests/data/config/ss.yaml | 2 +- clash/tests/data/config/trojan-grpc.json | 40 +++ clash/tests/data/config/trojan-ws.json | 20 ++ clash/tests/data/config/vmess-grpc.json | 39 +++ clash/tests/data/config/vmess-http2.json | 42 +++ clash/tests/data/config/vmess-ws.json | 25 ++ clash_lib/Cargo.toml | 1 + clash_lib/src/proxy/shadowsocks/mod.rs | 83 ++++++ clash_lib/src/proxy/trojan/mod.rs | 171 ++++++++++++ clash_lib/src/proxy/utils/mod.rs | 3 + .../proxy/utils/test_utils/config_helper.rs | 59 +++++ .../src/proxy/utils/test_utils/consts.rs | 10 + .../proxy/utils/test_utils/docker_runner.rs | 158 +++++++++++ clash_lib/src/proxy/utils/test_utils/mod.rs | 149 +++++++++++ clash_lib/src/proxy/vmess/mod.rs | 250 ++++++++++++++++++ 18 files changed, 1193 insertions(+), 1 deletion(-) create mode 100644 clash/tests/data/config/example.org-key.pem create mode 100644 clash/tests/data/config/example.org.pem create mode 100644 clash/tests/data/config/trojan-grpc.json create mode 100644 clash/tests/data/config/trojan-ws.json create mode 100644 clash/tests/data/config/vmess-grpc.json create mode 100644 clash/tests/data/config/vmess-http2.json create mode 100644 clash/tests/data/config/vmess-ws.json create mode 100644 clash_lib/src/proxy/utils/test_utils/config_helper.rs create mode 100644 clash_lib/src/proxy/utils/test_utils/consts.rs create mode 100644 clash_lib/src/proxy/utils/test_utils/docker_runner.rs create mode 100644 clash_lib/src/proxy/utils/test_utils/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6bfa3db4..44406c2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -695,6 +695,50 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bollard" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83545367eb6428eb35c29cdec3a1f350fa8d6d9085d59a7d7bcb637f2e38db5a" +dependencies = [ + "base64 0.21.7", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "http 1.1.0", + "http-body-util", + "hyper 1.1.0", + "hyper-named-pipe", + "hyper-util", + "hyperlocal-next", + "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.44.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709d9aa1c37abb89d40f19f5d0ad6f0d88cb1581264e571c9350fc5bb89cf1c5" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + [[package]] name = "boring" version = "4.5.0" @@ -1037,6 +1081,7 @@ dependencies = [ "axum 0.7.4", "axum-macros", "base64 0.22.0", + "bollard", "boring", "boring-sys", "boringtun", @@ -2672,6 +2717,22 @@ dependencies = [ "itoa", "pin-project-lite", "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper 1.1.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", ] [[package]] @@ -2717,9 +2778,26 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", "tracing", ] +[[package]] +name = "hyperlocal-next" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf569d43fa9848e510358c07b80f4adf34084ddc28c6a4a651ee8474c070dcc" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.1.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.59" @@ -4690,6 +4768,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "serde_spanned" version = "0.6.5" diff --git a/clash/tests/data/config/example.org-key.pem b/clash/tests/data/config/example.org-key.pem new file mode 100644 index 00000000..dbe9a3db --- /dev/null +++ b/clash/tests/data/config/example.org-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5 +5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo +PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE +sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R +i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5 +LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge +gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+ +y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO +jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z +ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv +H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG +o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ +CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49 +aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33 +bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7 +Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh +ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO +pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT +4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi +GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA +vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB +fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z +zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X +DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28 +9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP +XCar+uxMBXI1zbXqd9QdEwy4Ig== +-----END PRIVATE KEY----- diff --git a/clash/tests/data/config/example.org.pem b/clash/tests/data/config/example.org.pem new file mode 100644 index 00000000..9b99259a --- /dev/null +++ b/clash/tests/data/config/example.org.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB +hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh +bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl +cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz +MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl +bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy +by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8 +3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI +YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b +IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo +UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2 +I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln +F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP +TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa +Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1 +Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz +Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG +WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6 +AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6 +gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS +tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w= +-----END CERTIFICATE----- diff --git a/clash/tests/data/config/ss.yaml b/clash/tests/data/config/ss.yaml index b93a3185..55709606 100644 --- a/clash/tests/data/config/ss.yaml +++ b/clash/tests/data/config/ss.yaml @@ -60,5 +60,5 @@ proxies: udp: true rules: - - MATCH, ss + - MATCH, ss-01 ... diff --git a/clash/tests/data/config/trojan-grpc.json b/clash/tests/data/config/trojan-grpc.json new file mode 100644 index 00000000..eb0dcc99 --- /dev/null +++ b/clash/tests/data/config/trojan-grpc.json @@ -0,0 +1,40 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "trojan", + "settings": { + "clients": [ + { + "password": "example", + "email": "grpc@example.com" + } + ] + }, + "streamSettings": { + "network": "grpc", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "grpcSettings": { + "serviceName": "example" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/clash/tests/data/config/trojan-ws.json b/clash/tests/data/config/trojan-ws.json new file mode 100644 index 00000000..efc0acbd --- /dev/null +++ b/clash/tests/data/config/trojan-ws.json @@ -0,0 +1,20 @@ +{ + "run_type": "server", + "local_addr": "0.0.0.0", + "local_port": 10002, + "disable_http_check": true, + "password": [ + "example" + ], + "websocket": { + "enabled": true, + "path": "/", + "host": "example.org" + }, + "ssl": { + "verify": true, + "cert": "/fullchain.pem", + "key": "/privkey.pem", + "sni": "example.org" + } +} \ No newline at end of file diff --git a/clash/tests/data/config/vmess-grpc.json b/clash/tests/data/config/vmess-grpc.json new file mode 100644 index 00000000..178e0685 --- /dev/null +++ b/clash/tests/data/config/vmess-grpc.json @@ -0,0 +1,39 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811" + } + ] + }, + "streamSettings": { + "network": "grpc", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "grpcSettings": { + "serviceName": "example!" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/clash/tests/data/config/vmess-http2.json b/clash/tests/data/config/vmess-http2.json new file mode 100644 index 00000000..c6916a1b --- /dev/null +++ b/clash/tests/data/config/vmess-http2.json @@ -0,0 +1,42 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811" + } + ] + }, + "streamSettings": { + "network": "http", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "httpSettings": { + "host": [ + "example.org" + ], + "path": "/test" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/clash/tests/data/config/vmess-ws.json b/clash/tests/data/config/vmess-ws.json new file mode 100644 index 00000000..2bcb604d --- /dev/null +++ b/clash/tests/data/config/vmess-ws.json @@ -0,0 +1,25 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811" + } + ] + }, + "streamSettings": { + "network": "ws", + "security": "none" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} \ No newline at end of file diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index 81d45c52..eb9d88e0 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -118,6 +118,7 @@ ctor = "0.2" mockall = "0.12.1" tokio-test = "0.4.4" axum-macros = "0.4.0" +bollard = "0.16" [target.'cfg(macos)'.dependencies] diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index f3e42e74..c715137a 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -287,3 +287,86 @@ impl OutboundHandler for Handler { Ok(Box::new(d)) } } + +#[cfg(test)] +mod tests { + + use super::super::utils::test_utils::{ + benchmark_proxy, + consts::*, + docker_runner::{default_export_ports, default_host_config, DockerTestRunner}, + latency_test_proxy, LatencyTestOption, + }; + + use super::*; + + const PASSWORD: &str = "FzcLbKs2dY9mhL"; + const CIPHER: &str = "aes-256-gcm"; + + async fn get_runner() -> anyhow::Result { + use bollard::{container::Config, image::CreateImageOptions}; + + let host_config = default_host_config(); + let export_ports = default_export_ports(); + + DockerTestRunner::new( + Some(CreateImageOptions { + from_image: IMAGE_SS_RUST, + ..Default::default() + }), + Config { + image: Some(IMAGE_SS_RUST), + tty: Some(true), + entrypoint: Some(vec!["ssserver"]), + cmd: Some(vec![ + "-s", + "0.0.0.0:10002", + "-m", + CIPHER, + "-k", + PASSWORD, + "-U", + ]), + exposed_ports: Some(export_ports), + host_config: Some(host_config), + ..Default::default() + }, + ) + .await + .map_err(Into::into) + } + + #[tokio::test] + async fn test_ss() -> anyhow::Result<()> { + let opts = HandlerOptions { + name: "test-ss".to_owned(), + common_opts: Default::default(), + server: LOCAL_ADDR.to_owned(), + port: 10002, + password: PASSWORD.to_owned(), + cipher: CIPHER.to_owned(), + plugin_opts: Default::default(), + udp: false, + }; + let handler = Handler::new(opts); + + let watch = get_runner().await?; + + watch + .run_and_cleanup(async move { + benchmark_proxy(handler.clone(), 10001).await?; + latency_test_proxy( + handler, + LatencyTestOption { + dst: SocksAddr::Domain("google.com".to_owned(), 80), + req: GOOGLE_REQ, + expected_resp: GOOGLE_RESP_301, + read_exact: true, + }, + ) + .await?; + Ok(()) + }) + .await + } +} diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index b11915da..d2d7b79f 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -219,3 +219,174 @@ impl OutboundHandler for Handler { Ok(Box::new(chained)) } } + +#[cfg(test)] +mod tests { + + use std::collections::HashMap; + + use crate::proxy::utils::test_utils::{ + benchmark_proxy, + config_helper::test_config_base_dir, + consts::*, + docker_runner::{ + default_export_ports, default_host_config, mount_config, DockerTestRunner, + }, + latency_test_proxy, LatencyTestOption, + }; + + use super::*; + + async fn get_ws_runner() -> anyhow::Result { + use bollard::{container::Config, image::CreateImageOptions}; + + let mut host_config = default_host_config(); + let test_config_dir = test_config_base_dir(); + let trojan_conf = test_config_dir.join("trojan-ws.json"); + let trojan_cert = test_config_dir.join("example.org.pem"); + let trojan_key = test_config_dir.join("example.org-key.pem"); + + host_config.mounts = Some(mount_config(&[ + (trojan_conf.to_str().unwrap(), "/etc/trojan-go/config.json"), + (trojan_cert.to_str().unwrap(), "/fullchain.pem"), + (trojan_key.to_str().unwrap(), "/privkey.pem"), + ])); + let export_ports = default_export_ports(); + + DockerTestRunner::new( + Some(CreateImageOptions { + from_image: IMAGE_TROJAN_GO, + ..Default::default() + }), + Config { + image: Some(IMAGE_TROJAN_GO), + tty: Some(true), + exposed_ports: Some(export_ports), + host_config: Some(host_config), + ..Default::default() + }, + ) + .await + .map_err(Into::into) + } + + #[tokio::test] + async fn test_trojan_ws() -> anyhow::Result<()> { + let opts = Opts { + name: "test-trojan-ws".to_owned(), + common_opts: Default::default(), + server: "127.0.0.1".to_owned(), + port: 10002, + password: "example".to_owned(), + udp: true, + sni: "example.org".to_owned(), + alpn: None, + skip_cert_verify: true, + transport: Some(Transport::Ws(WsOption { + path: "".to_owned(), + headers: [("Host".to_owned(), "example.org".to_owned())] + .into_iter() + .collect::>(), + // ignore the rest by setting max_early_data to 0 + max_early_data: 0, + early_data_header_name: "".to_owned(), + })), + }; + let handler = Handler::new(opts); + + let runner = get_ws_runner().await?; + + runner + .run_and_cleanup(async move { + benchmark_proxy(handler.clone(), 10001).await?; + latency_test_proxy( + handler, + LatencyTestOption { + dst: SocksAddr::Domain("google.com".to_owned(), 80), + req: GOOGLE_REQ, + expected_resp: GOOGLE_RESP_301, + read_exact: true, + }, + ) + .await?; + Ok(()) + }) + .await?; + + Ok(()) + } + + async fn get_grpc_runner() -> anyhow::Result { + use bollard::{container::Config, image::CreateImageOptions}; + + let mut host_config = default_host_config(); + let test_config_dir = test_config_base_dir(); + let conf = test_config_dir.join("trojan-grpc.json"); + let cert = test_config_dir.join("example.org.pem"); + let key = test_config_dir.join("example.org-key.pem"); + + host_config.mounts = Some(mount_config(&[ + (conf.to_str().unwrap(), "/etc/xray/config.json"), + (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), + (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), + ])); + let export_ports = default_export_ports(); + + DockerTestRunner::new( + Some(CreateImageOptions { + from_image: IMAGE_XRAY, + ..Default::default() + }), + Config { + image: Some(IMAGE_XRAY), + tty: Some(true), + exposed_ports: Some(export_ports), + host_config: Some(host_config), + ..Default::default() + }, + ) + .await + .map_err(Into::into) + } + + #[tokio::test] + async fn test_trojan_grpc() -> anyhow::Result<()> { + let opts = Opts { + name: "test-trojan-grpc".to_owned(), + common_opts: Default::default(), + server: "127.0.0.1".to_owned(), + port: 10002, + password: "example".to_owned(), + udp: true, + sni: "example.org".to_owned(), + alpn: None, + skip_cert_verify: true, + transport: Some(Transport::Grpc(GrpcOption { + host: "example.org".to_owned(), + service_name: "example".to_owned(), + })), + }; + let handler = Handler::new(opts); + + let runner = get_grpc_runner().await?; + + runner + .run_and_cleanup(async move { + benchmark_proxy(handler.clone(), 10001).await?; + latency_test_proxy( + handler, + LatencyTestOption { + dst: SocksAddr::Domain("google.com".to_owned(), 80), + req: GOOGLE_REQ, + expected_resp: GOOGLE_RESP_301, + read_exact: true, + }, + ) + .await?; + Ok(()) + }) + .await?; + + Ok(()) + } +} diff --git a/clash_lib/src/proxy/utils/mod.rs b/clash_lib/src/proxy/utils/mod.rs index 01b86bbd..6fbadf57 100644 --- a/clash_lib/src/proxy/utils/mod.rs +++ b/clash_lib/src/proxy/utils/mod.rs @@ -3,6 +3,9 @@ use std::{ net::{IpAddr, SocketAddr}, }; +#[cfg(test)] +pub mod test_utils; + pub mod provider_helper; mod socket_helpers; diff --git a/clash_lib/src/proxy/utils/test_utils/config_helper.rs b/clash_lib/src/proxy/utils/test_utils/config_helper.rs new file mode 100644 index 00000000..e6c34271 --- /dev/null +++ b/clash_lib/src/proxy/utils/test_utils/config_helper.rs @@ -0,0 +1,59 @@ +use crate::Error; +use std::path::PathBuf; +use std::sync::Arc; +use tracing::debug; + +use crate::{ + app::{ + dns::{self, ClashResolver, SystemResolver}, + profile, + }, + common::{http::new_http_client, mmdb}, + Config, +}; + +pub fn root_dir() -> PathBuf { + let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR").to_owned()); + // remove the clash_lib + root.pop(); + root +} + +pub fn test_config_base_dir() -> PathBuf { + root_dir().join("clash/tests/data/config") +} + +// load the config from test dir +// and return the dns resolver for the proxy +pub async fn load_config() -> anyhow::Result<( + crate::config::internal::config::Config, + Arc, +)> { + let root = root_dir(); + let test_base_dir = test_config_base_dir(); + let config_path = test_base_dir.join("ss.yaml").to_str().unwrap().to_owned(); + let config = Config::File(config_path).try_parse()?; + let mmdb_path = test_base_dir.join("Country.mmdb"); + let system_resolver = + Arc::new(SystemResolver::new().map_err(|x| Error::DNSError(x.to_string()))?); + let client = new_http_client(system_resolver).map_err(|x| Error::DNSError(x.to_string()))?; + + let mmdb = Arc::new( + mmdb::Mmdb::new(mmdb_path, config.general.mmdb_download_url.clone(), client).await?, + ); + + debug!("initializing cache store"); + let cache_store = profile::ThreadSafeCacheFile::new( + PathBuf::from(root) + .join("cache.db") + .as_path() + .to_str() + .unwrap(), + config.profile.store_selected, + ); + + let dns_resolver: Arc = + dns::Resolver::new_resolver(&config.dns, cache_store.clone(), mmdb.clone()).await; + + Ok((config, dns_resolver)) +} diff --git a/clash_lib/src/proxy/utils/test_utils/consts.rs b/clash_lib/src/proxy/utils/test_utils/consts.rs new file mode 100644 index 00000000..e9416cf4 --- /dev/null +++ b/clash_lib/src/proxy/utils/test_utils/consts.rs @@ -0,0 +1,10 @@ +pub const LOCAL_ADDR: &str = "127.0.0.1"; +pub const GOOGLE_REQ: &[u8] = b"GET / HTTP/1.1\r\nHost: google.com\r\nAccept: */*\r\n\r\n"; +pub const EXAMPLE_REQ: &[u8] = b"GET / HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"; +pub const GOOGLE_RESP_301: &[u8] = b"HTTP/1.1 301"; +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_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 new file mode 100644 index 00000000..69df25ac --- /dev/null +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -0,0 +1,158 @@ +//! This example will run a non-interactive command inside the container using `docker exec` + +use std::collections::HashMap; + +use bollard::container::{Config, RemoveContainerOptions}; +use bollard::secret::{HostConfig, Mount, PortBinding}; +use bollard::Docker; + +use bollard::exec::{CreateExecOptions, StartExecResults}; +use bollard::image::CreateImageOptions; + +use anyhow::Result; +use futures::{Future, StreamExt, TryStreamExt}; + +pub struct DockerTestRunner { + _instance: Docker, + id: String, +} + +impl DockerTestRunner { + pub async fn new< + 'a, + T1: serde::ser::Serialize + Into + std::fmt::Debug + Clone, + Z: Into + std::hash::Hash + Eq + serde::Serialize, + >( + image_conf: Option>, + container_conf: Config, + ) -> Result { + let docker: Docker = Docker::connect_with_socket_defaults()?; + + docker + .create_image(image_conf, None, None) + .try_collect::>() + .await?; + + let id = docker + .create_container::<&str, Z>(None, container_conf) + .await? + .id; + docker.start_container::(&id, None).await?; + Ok(Self { + _instance: docker, + id, + }) + } + + #[allow(unused)] + pub async fn exec(&self, cmd: Vec<&str>) -> anyhow::Result<()> { + // non interactive + let exec = self + ._instance + .create_exec( + &self.id, + CreateExecOptions { + attach_stdout: Some(true), + attach_stderr: Some(true), + cmd: Some(cmd), + ..Default::default() + }, + ) + .await? + .id; + if let StartExecResults::Attached { mut output, .. } = + self._instance.start_exec(&exec, None).await? + { + while let Some(Ok(msg)) = output.next().await { + print!("{msg}"); + } + return Ok(()); + } else { + anyhow::bail!("failed to execute cmd") + } + } + + // will make sure the container is cleaned up after the future is finished + pub async fn run_and_cleanup( + self, + f: impl Future> + ) -> anyhow::Result<()> { + let fut = Box::pin(f); + let res = fut.await; + // make sure the container is cleaned up + // TODO: select a timeout future as well, make sure it can quit smoothly + self.cleanup().await?; + + 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) + } +} + +pub fn mount_config(pairs: &[(&str, &str)]) -> Vec { + pairs + .iter() + .map(|(src, dst)| Mount { + target: Some(dst.to_string()), + source: Some(src.to_string()), + typ: Some(bollard::secret::MountTypeEnum::BIND), + read_only: Some(false), + ..Default::default() + }) + .collect::>() +} + +pub fn default_host_config() -> HostConfig { + let mut host_config = HostConfig::default(); + // we need to use the host mode to enable the benchmark function + #[cfg(not(target_os = "macos"))] + { + host_config.network_mode = Some("host".to_owned()); + } + host_config.port_bindings = Some( + [ + ( + "10002/tcp".to_owned(), + Some(vec![PortBinding { + host_ip: Some("0.0.0.0".to_owned()), + host_port: Some("10002".to_owned()), + }]), + ), + ( + "10002/udp".to_owned(), + Some(vec![PortBinding { + host_ip: Some("0.0.0.0".to_owned()), + host_port: Some("10002".to_owned()), + }]), + ), + ] + .into_iter() + .collect::>(), + ); + + host_config +} + +pub fn default_export_ports() -> HashMap<&'static str, HashMap<(), ()>> { + let export_ports: HashMap<&str, HashMap<(), ()>> = [ + ("10002/tcp", Default::default()), + ("10002/udp", Default::default()), + ] + .into_iter() + .collect::>(); + + export_ports +} diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs new file mode 100644 index 00000000..197ca4b2 --- /dev/null +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -0,0 +1,149 @@ +use std::sync::Arc; + +use tokio::{ + io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + join, + net::TcpListener, +}; +use crate::{app::dispatcher::ChainedStream, proxy::OutboundHandler, session::{Session, SocksAddr}}; + +pub mod config_helper; +pub mod consts; +pub mod docker_runner; + +// TODO: add the thoroughput metrics +pub async fn benchmark_proxy(handler: Arc, port: u16) -> anyhow::Result<()> { + // proxy -> proxy-server -> destination(127.0.0.1:port) + + // the destination is a local server + let sess = Session { + destination: ("127.0.0.1".to_owned(), port) + .try_into() + .unwrap_or_else(|_| panic!("")), + ..Default::default() + }; + + let (_, resolver) = config_helper::load_config().await?; + + let listener = TcpListener::bind(format!("0.0.0.0:{}", port).as_str()).await?; + + async fn destination_fn(incoming: T) -> anyhow::Result<()> + where + T: AsyncRead + AsyncWrite, + { + // Use inbound_stream here + let (mut read_half, mut write_half) = split(incoming); + let chunk = "world"; + let mut buf = vec![0; 5]; + + for _ in 0..100 { + read_half.read_exact(&mut buf).await?; + assert_eq!(&buf, b"hello"); + } + + for _ in 0..100 { + write_half.write_all(chunk.as_bytes()).await?; + write_half.flush().await?; + } + Ok(()) + } + + let target_local_server_handler = tokio::spawn(async move { + match listener.accept().await { + Ok((stream, _)) => match destination_fn(stream).await { + Ok(_) => {} + Err(e) => eprintln!("Failed to serve: {}", e), + }, + Err(e) => { + // Handle error e, log it, or ignore it + eprintln!("Failed to accept connection: {}", e); + } + } + println!("server task finished"); + }); + + async fn proxy_fn(stream: Box) -> anyhow::Result<()> { + let (mut read_half, mut write_half) = split(stream); + + let chunk = "hello"; + let mut buf = vec![0; 5]; + + for _ in 0..100 { + write_half.write_all(chunk.as_bytes()).await?; + } + write_half.flush().await?; + drop(write_half); + + for _ in 0..100 { + read_half.read_exact(&mut buf).await?; + assert_eq!(buf, "world".as_bytes().to_owned()); + } + drop(read_half); + Ok(()) + } + + let proxy_task = tokio::spawn(async move { + match handler.connect_stream(&sess, resolver).await { + Ok(stream) => match proxy_fn(stream).await { + Ok(_) => {} + Err(e) => eprintln!("Failed to to proxy: {}", e), + }, + Err(e) => eprintln!("Failed to accept connection: {}", e), + } + println!("proxy task finished"); + }); + + let _ = join!(proxy_task, target_local_server_handler); + + Ok(()) +} + +pub struct LatencyTestOption<'a> { + pub dst: SocksAddr, + pub req: &'a [u8], + pub expected_resp: &'a [u8], + pub read_exact: bool, +} + +pub async fn latency_test_proxy( + handler: Arc, + option: LatencyTestOption<'_>, +) -> anyhow::Result<()> { + // proxy -> proxy-server -> destination(google.com) + + // the destination is a local server + let sess = Session { + destination: option.dst, + ..Default::default() + }; + + let (_, resolver) = config_helper::load_config().await?; + + let stream = handler.connect_stream(&sess, resolver).await?; + + let (mut read_half, mut write_half) = split(stream); + + write_half.write_all(option.req).await?; + write_half.flush().await?; + drop(write_half); + + let start_time = std::time::SystemTime::now(); + let mut response = vec![0; option.expected_resp.len()]; + + if option.read_exact { + read_half.read_exact(&mut response).await?; + println!("response:\n{}", String::from_utf8_lossy(&response)); + assert_eq!(&response, option.expected_resp); + } else { + read_half.read_to_end(&mut response).await?; + println!("response:\n{}", String::from_utf8_lossy(&response)); + assert_eq!(&response, option.expected_resp); + } + + let end_time = std::time::SystemTime::now(); + println!( + "time cost:{:?}", + end_time.duration_since(start_time).unwrap() + ); + Ok(()) +} diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index 30fc4e50..de0dc142 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -250,3 +250,253 @@ impl OutboundHandler for Handler { Ok(Box::new(chained)) } } + +#[cfg(test)] +mod tests { + + use crate::proxy::utils::test_utils::{ + benchmark_proxy, + config_helper::test_config_base_dir, + consts::*, + docker_runner::{ + default_export_ports, default_host_config, mount_config, DockerTestRunner, + }, + latency_test_proxy, LatencyTestOption, + }; + + use super::*; + + async fn get_ws_runner() -> anyhow::Result { + use bollard::{container::Config, image::CreateImageOptions}; + + let mut host_config = default_host_config(); + let test_config_dir = test_config_base_dir(); + let trojan_conf = test_config_dir.join("vmess-ws.json"); + + host_config.mounts = Some(mount_config(&[( + trojan_conf.to_str().unwrap(), + "/etc/v2ray/config.json", + )])); + let export_ports = default_export_ports(); + + DockerTestRunner::new( + Some(CreateImageOptions { + from_image: IMAGE_VMESS, + ..Default::default() + }), + Config { + image: Some(IMAGE_VMESS), + tty: Some(true), + exposed_ports: Some(export_ports), + host_config: Some(host_config), + ..Default::default() + }, + ) + .await + .map_err(Into::into) + } + + #[tokio::test] + async fn test_vmess_ws() -> anyhow::Result<()> { + let opts = HandlerOptions { + name: "test-vmess-ws".into(), + common_opts: Default::default(), + server: LOCAL_ADDR.into(), + port: 10002, + uuid: "b831381d-6324-4d53-ad4f-8cda48b30811".into(), + alter_id: 0, + security: "none".into(), + udp: true, + tls: None, + transport: Some(VmessTransport::Ws(WsOption { + path: "".to_owned(), + headers: [("Host".to_owned(), "example.org".to_owned())] + .into_iter() + .collect::>(), + // ignore the rest by setting max_early_data to 0 + max_early_data: 0, + early_data_header_name: "".to_owned(), + })), + }; + let handler = Handler::new(opts); + + let runner = get_ws_runner().await?; + + runner + .run_and_cleanup(async move { + benchmark_proxy(handler.clone(), 10001).await?; + latency_test_proxy( + handler, + LatencyTestOption { + dst: SocksAddr::Domain("google.com".to_owned(), 80), + req: GOOGLE_REQ, + expected_resp: GOOGLE_RESP_301, + read_exact: true, + }, + ) + .await?; + Ok(()) + }) + .await?; + + Ok(()) + } + + async fn get_grpc_runner() -> anyhow::Result { + use bollard::{container::Config, image::CreateImageOptions}; + + let mut host_config = default_host_config(); + let test_config_dir = test_config_base_dir(); + let conf = test_config_dir.join("vmess-grpc.json"); + let cert = test_config_dir.join("example.org.pem"); + let key = test_config_dir.join("example.org-key.pem"); + + host_config.mounts = Some(mount_config(&[ + (conf.to_str().unwrap(), "/etc/v2ray/config.json"), + (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), + (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), + ])); + + let export_ports = default_export_ports(); + + DockerTestRunner::new( + Some(CreateImageOptions { + from_image: IMAGE_VMESS, + ..Default::default() + }), + Config { + image: Some(IMAGE_VMESS), + tty: Some(true), + exposed_ports: Some(export_ports), + host_config: Some(host_config), + ..Default::default() + }, + ) + .await + .map_err(Into::into) + } + + #[tokio::test] + async fn test_vmess_grpc() -> anyhow::Result<()> { + let opts = HandlerOptions { + name: "test-vmess-grpc".into(), + common_opts: Default::default(), + server: LOCAL_ADDR.into(), + port: 10002, + uuid: "b831381d-6324-4d53-ad4f-8cda48b30811".into(), + alter_id: 0, + security: "auto".into(), + udp: true, + tls: Some(transport::TLSOptions { + skip_cert_verify: true, + sni: "example.org".into(), + alpn: None, + }), + transport: Some(VmessTransport::Grpc(GrpcOption { + host: "example.org".to_owned(), + service_name: "example!".to_owned(), + })), + }; + let handler = Handler::new(opts); + + let runner = get_grpc_runner().await?; + + runner + .run_and_cleanup(async move { + benchmark_proxy(handler.clone(), 10001).await?; + latency_test_proxy( + handler, + LatencyTestOption { + dst: SocksAddr::Domain("example.org".to_owned(), 80), + req: EXAMPLE_REQ, + expected_resp: EXAMLE_RESP_200, + read_exact: true, + }, + ) + .await?; + Ok(()) + }) + .await?; + + Ok(()) + } + + async fn get_h2_runner() -> anyhow::Result { + use bollard::{container::Config, image::CreateImageOptions}; + + let mut host_config = default_host_config(); + let test_config_dir = test_config_base_dir(); + let conf = test_config_dir.join("vmess-http2.json"); + let cert = test_config_dir.join("example.org.pem"); + let key = test_config_dir.join("example.org-key.pem"); + + host_config.mounts = Some(mount_config(&[ + (conf.to_str().unwrap(), "/etc/v2ray/config.json"), + (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), + (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), + ])); + + let export_ports = default_export_ports(); + + DockerTestRunner::new( + Some(CreateImageOptions { + from_image: IMAGE_VMESS, + ..Default::default() + }), + Config { + image: Some(IMAGE_VMESS), + tty: Some(true), + exposed_ports: Some(export_ports), + host_config: Some(host_config), + ..Default::default() + }, + ) + .await + .map_err(Into::into) + } + + #[tokio::test] + async fn test_vmess_h2() -> anyhow::Result<()> { + let opts = HandlerOptions { + name: "test-vmess-h2".into(), + common_opts: Default::default(), + server: LOCAL_ADDR.into(), + port: 10002, + uuid: "b831381d-6324-4d53-ad4f-8cda48b30811".into(), + alter_id: 0, + security: "auto".into(), + udp: false, + tls: Some(transport::TLSOptions { + skip_cert_verify: true, + sni: "example.org".into(), + alpn: None, + }), + transport: Some(VmessTransport::H2(Http2Option { + host: vec!["example.org".into()], + path: "/testlollol".into(), + })), + }; + let handler = Handler::new(opts); + + let runner = get_h2_runner().await?; + + runner + .run_and_cleanup(async move { + benchmark_proxy(handler.clone(), 10001).await?; + latency_test_proxy( + handler, + LatencyTestOption { + dst: SocksAddr::Domain("google.com".to_owned(), 80), + req: GOOGLE_REQ, + expected_resp: GOOGLE_RESP_301, + read_exact: true, + }, + ) + .await?; + Ok(()) + }) + .await?; + + Ok(()) + } +} From 051b460645c4e7de6e5aa53f95655aa2253409ac Mon Sep 17 00:00:00 2001 From: V Date: Sun, 17 Mar 2024 17:56:54 +0800 Subject: [PATCH 02/24] fix opt.host in http2 config (#323) --- clash_lib/src/proxy/vmess/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index de0dc142..9847c451 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -94,7 +94,7 @@ impl Handler { }; let h2_builder = Http2Config { - hosts: vec![self.opts.server.clone()], + hosts: opt.host.clone(), method: http::Method::GET, headers: HashMap::new(), path: opt.path.to_owned().try_into().expect("invalid H2 path"), From 6f7d131413dcdcd1dd115c998361c767d38089f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:54:42 +0000 Subject: [PATCH 03/24] Bump clap from 4.5.2 to 4.5.3 (#330) Bumps [clap](https://github.com/clap-rs/clap) from 4.5.2 to 4.5.3. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/v4.5.2...v4.5.3) --- updated-dependencies: - dependency-name: clap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 38 ++++++++++++++++++++++---------------- clash/Cargo.toml | 2 +- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44406c2e..e4a0f335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,7 +512,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.48", @@ -1015,9 +1015,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" dependencies = [ "clap_builder", "clap_derive", @@ -1037,11 +1037,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.48", @@ -1057,7 +1057,7 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" name = "clash" version = "0.1.15" dependencies = [ - "clap 4.5.2", + "clap 4.5.3", "clash_lib", ] @@ -1310,7 +1310,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.2", + "clap 4.5.3", "criterion-plot", "futures", "is-terminal", @@ -1601,7 +1601,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5283ac2881753c76c0892406705553f0d9ab30649f81e18964d3408f4501edb8" dependencies = [ "derive-adhoc-macros 0.7.3", - "heck", + "heck 0.4.1", ] [[package]] @@ -1611,7 +1611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acd539187faa4741d0b5cee4bfe866b655cbe3cf497ecbe60db60883108e19fb" dependencies = [ "derive-adhoc-macros 0.8.1", - "heck", + "heck 0.4.1", ] [[package]] @@ -1620,7 +1620,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c21b673a9b8c78c34908e6fcb42b922e11c4df2de5237f1c3f58d3285904a84b" dependencies = [ - "heck", + "heck 0.4.1", "itertools 0.11.0", "proc-macro-crate 1.3.1", "proc-macro2", @@ -1637,7 +1637,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0f27b6d3c8bc4e69b0e1091cfc38e3bb9e7b6d3dfc3703a1dd5c75d0a281e2a" dependencies = [ - "heck", + "heck 0.4.1", "itertools 0.12.0", "proc-macro-crate 2.0.0", "proc-macro2", @@ -1879,7 +1879,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -1891,7 +1891,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.48", @@ -2421,6 +2421,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -5167,7 +5173,7 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -5180,7 +5186,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", diff --git a/clash/Cargo.toml b/clash/Cargo.toml index 648aa96e..5bfec2cb 100644 --- a/clash/Cargo.toml +++ b/clash/Cargo.toml @@ -9,6 +9,6 @@ name = "clash" path = "src/main.rs" [dependencies] -clap = { version = "4.5.2", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } clash_lib = { path = "../clash_lib", version = "0.1" } \ No newline at end of file From 1053cad08948f7ca2fe2c2d2b32a1bfaa66d1298 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 05:18:16 +0000 Subject: [PATCH 04/24] Bump h2 from 0.4.2 to 0.4.3 (#328) Bumps [h2](https://github.com/hyperium/h2) from 0.4.2 to 0.4.3. - [Release notes](https://github.com/hyperium/h2/releases) - [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/h2/compare/v0.4.2...v0.4.3) --- updated-dependencies: - dependency-name: h2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- clash_lib/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4a0f335..57333c9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1100,7 +1100,7 @@ dependencies = [ "filetime", "foreign-types-shared", "futures", - "h2 0.4.2", + "h2 0.4.3", "hickory-client", "hickory-proto", "hickory-resolver", @@ -2310,9 +2310,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" dependencies = [ "bytes", "fnv", @@ -2715,7 +2715,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.2", + "h2 0.4.3", "http 1.1.0", "http-body 1.0.0", "httparse", diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index eb9d88e0..881fc100 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -27,7 +27,7 @@ lru_time_cache = "0.11" hyper = { version = "0.14.28", features = ["http1","http2","client", "server", "tcp"] } http = { version = "1.1" } httparse = "1.8.0" -h2 = "0.4.2" +h2 = "0.4.3" prost = "0.12" tower = { version = "0.4", features = ["util"] } libc = "0.2" From 35dcef52f5f79583612489d00982cfd585335389 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:09:18 +0000 Subject: [PATCH 05/24] Bump async-trait from 0.1.77 to 0.1.78 (#329) Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.77 to 0.1.78. - [Release notes](https://github.com/dtolnay/async-trait/releases) - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.77...0.1.78) --- updated-dependencies: - dependency-name: async-trait dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57333c9a..f482e9c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,9 +343,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" dependencies = [ "proc-macro2", "quote", From f36331c0c8cf00e75a54efb0bbd88a6a07035530 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:58:05 +0000 Subject: [PATCH 06/24] Bump serde_yaml from 0.9.32 to 0.9.33 (#326) Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.9.32 to 0.9.33. - [Release notes](https://github.com/dtolnay/serde-yaml/releases) - [Commits](https://github.com/dtolnay/serde-yaml/compare/0.9.32...0.9.33) --- updated-dependencies: - dependency-name: serde_yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f482e9c1..532d97c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4837,9 +4837,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.32" +version = "0.9.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" dependencies = [ "indexmap 2.2.1", "itoa", @@ -6702,9 +6702,9 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" From 0e71cbaa811b0facfd39eba5538b4bcee48fd970 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:03:14 +1100 Subject: [PATCH 07/24] Bump brotli from 3.4.0 to 3.5.0 (#327) Bumps [brotli](https://github.com/dropbox/rust-brotli) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/dropbox/rust-brotli/releases) - [Commits](https://github.com/dropbox/rust-brotli/commits) --- updated-dependencies: - dependency-name: brotli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- clash_lib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 532d97c9..5d3a4180 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -795,9 +795,9 @@ checksum = "2225b558afc76c596898f5f1b3fc35cfce0eb1b13635cbd7d1b2a7177dc10ccd" [[package]] name = "brotli" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index 881fc100..64569282 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -50,7 +50,7 @@ opentelemetry-jaeger = { version = "0.21", features = ["collector_client", "hype opentelemetry-otlp = { version = "0.15.0", features = ["http-proto"] } crc32fast = "1.4.0" -brotli = "3.4.0" +brotli = "3.5.0" hmac = "0.12.1" sha2 = "0.10.8" md-5 = "0.10.5" From 931d0c97959e82baf35d0f26e2f064a6d7a70ab5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:55:28 +1100 Subject: [PATCH 08/24] Bump async-recursion from 1.0.5 to 1.1.0 (#325) Bumps [async-recursion](https://github.com/dcchut/async-recursion) from 1.0.5 to 1.1.0. - [Release notes](https://github.com/dcchut/async-recursion/releases) - [Commits](https://github.com/dcchut/async-recursion/compare/v1.0.5...v1.1.0) --- updated-dependencies: - dependency-name: async-recursion dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d3a4180..b49dd9fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,9 +310,9 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", From 3e60e5a38ed5b1fb3d710ecf9629773dfa295c77 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Mon, 18 Mar 2024 23:33:58 +0800 Subject: [PATCH 09/24] optimize docker test structure; use serial test to make sure the correctness --- Cargo.lock | 39 +++++ clash_lib/Cargo.toml | 2 +- clash_lib/src/proxy/shadowsocks/mod.rs | 45 ++---- clash_lib/src/proxy/trojan/mod.rs | 74 +++------- .../proxy/utils/test_utils/docker_runner.rs | 139 ++++++++++++++---- clash_lib/src/proxy/vmess/mod.rs | 106 ++++--------- 6 files changed, 210 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b49dd9fa..19a29dde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,6 +1136,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "serial_test", "sha2", "shadowsocks", "smoltcp", @@ -1535,6 +1536,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.9", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -4848,6 +4862,31 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serial_test" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot 0.12.1", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "sha1" version = "0.10.6" diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index 64569282..ad6a9fbc 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -119,7 +119,7 @@ mockall = "0.12.1" tokio-test = "0.4.4" axum-macros = "0.4.0" bollard = "0.16" - +serial_test = "3.0.0" [target.'cfg(macos)'.dependencies] security-framework = "2.8.0" diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index c715137a..d6abd04b 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -291,11 +291,11 @@ impl OutboundHandler for Handler { #[cfg(test)] mod tests { + use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; + use super::super::utils::test_utils::{ - benchmark_proxy, - consts::*, - docker_runner::{default_export_ports, default_host_config, DockerTestRunner}, - latency_test_proxy, LatencyTestOption, + benchmark_proxy, consts::*, docker_runner::DockerTestRunner, latency_test_proxy, + LatencyTestOption, }; use super::*; @@ -304,39 +304,16 @@ mod tests { const CIPHER: &str = "aes-256-gcm"; async fn get_runner() -> anyhow::Result { - use bollard::{container::Config, image::CreateImageOptions}; - - let host_config = default_host_config(); - let export_ports = default_export_ports(); - - DockerTestRunner::new( - Some(CreateImageOptions { - from_image: IMAGE_SS_RUST, - ..Default::default() - }), - Config { - image: Some(IMAGE_SS_RUST), - tty: Some(true), - entrypoint: Some(vec!["ssserver"]), - cmd: Some(vec![ - "-s", - "0.0.0.0:10002", - "-m", - CIPHER, - "-k", - PASSWORD, - "-U", - ]), - exposed_ports: Some(export_ports), - host_config: Some(host_config), - ..Default::default() - }, - ) - .await - .map_err(Into::into) + DockerTestRunnerBuilder::new() + .image(IMAGE_SS_RUST) + .entrypoint(&["ssserver"]) + .cmd(&["-s", "0.0.0.0:10002", "-m", CIPHER, "-k", PASSWORD, "-U"]) + .build() + .await } #[tokio::test] + #[serial_test::serial] async fn test_ss() -> anyhow::Result<()> { let opts = HandlerOptions { name: "test-ss".to_owned(), diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index d2d7b79f..2b3c8c32 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -229,48 +229,31 @@ mod tests { benchmark_proxy, config_helper::test_config_base_dir, consts::*, - docker_runner::{ - default_export_ports, default_host_config, mount_config, DockerTestRunner, - }, + docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, latency_test_proxy, LatencyTestOption, }; use super::*; async fn get_ws_runner() -> anyhow::Result { - use bollard::{container::Config, image::CreateImageOptions}; - - let mut host_config = default_host_config(); let test_config_dir = test_config_base_dir(); let trojan_conf = test_config_dir.join("trojan-ws.json"); let trojan_cert = test_config_dir.join("example.org.pem"); let trojan_key = test_config_dir.join("example.org-key.pem"); - host_config.mounts = Some(mount_config(&[ - (trojan_conf.to_str().unwrap(), "/etc/trojan-go/config.json"), - (trojan_cert.to_str().unwrap(), "/fullchain.pem"), - (trojan_key.to_str().unwrap(), "/privkey.pem"), - ])); - let export_ports = default_export_ports(); - - DockerTestRunner::new( - Some(CreateImageOptions { - from_image: IMAGE_TROJAN_GO, - ..Default::default() - }), - Config { - image: Some(IMAGE_TROJAN_GO), - tty: Some(true), - exposed_ports: Some(export_ports), - host_config: Some(host_config), - ..Default::default() - }, - ) - .await - .map_err(Into::into) + DockerTestRunnerBuilder::new() + .image(IMAGE_TROJAN_GO) + .mounts(&[ + (trojan_conf.to_str().unwrap(), "/etc/trojan-go/config.json"), + (trojan_cert.to_str().unwrap(), "/fullchain.pem"), + (trojan_key.to_str().unwrap(), "/privkey.pem"), + ]) + .build() + .await } #[tokio::test] + #[serial_test::serial] async fn test_trojan_ws() -> anyhow::Result<()> { let opts = Opts { name: "test-trojan-ws".to_owned(), @@ -317,39 +300,24 @@ mod tests { } async fn get_grpc_runner() -> anyhow::Result { - use bollard::{container::Config, image::CreateImageOptions}; - - let mut host_config = default_host_config(); let test_config_dir = test_config_base_dir(); let conf = test_config_dir.join("trojan-grpc.json"); let cert = test_config_dir.join("example.org.pem"); let key = test_config_dir.join("example.org-key.pem"); - host_config.mounts = Some(mount_config(&[ - (conf.to_str().unwrap(), "/etc/xray/config.json"), - (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), - (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), - ])); - let export_ports = default_export_ports(); - - DockerTestRunner::new( - Some(CreateImageOptions { - from_image: IMAGE_XRAY, - ..Default::default() - }), - Config { - image: Some(IMAGE_XRAY), - tty: Some(true), - exposed_ports: Some(export_ports), - host_config: Some(host_config), - ..Default::default() - }, - ) - .await - .map_err(Into::into) + DockerTestRunnerBuilder::new() + .image(IMAGE_XRAY) + .mounts(&[ + (conf.to_str().unwrap(), "/etc/xray/config.json"), + (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), + (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), + ]) + .build() + .await } #[tokio::test] + #[serial_test::serial] async fn test_trojan_grpc() -> anyhow::Result<()> { let opts = Opts { name: "test-trojan-grpc".to_owned(), 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 69df25ac..00fb60e0 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -75,7 +75,7 @@ impl DockerTestRunner { // will make sure the container is cleaned up after the future is finished pub async fn run_and_cleanup( self, - f: impl Future> + f: impl Future>, ) -> anyhow::Result<()> { let fut = Box::pin(f); let res = fut.await; @@ -102,20 +102,114 @@ impl DockerTestRunner { } } -pub fn mount_config(pairs: &[(&str, &str)]) -> Vec { - pairs - .iter() - .map(|(src, dst)| Mount { - target: Some(dst.to_string()), - source: Some(src.to_string()), - typ: Some(bollard::secret::MountTypeEnum::BIND), - read_only: Some(false), - ..Default::default() - }) - .collect::>() +const PORT: u16 = 10002; +const EXPOSED_TCP: &str = "10002/tcp"; +const EXPOSED_UDP: &str = "10002/udp"; +const EXPOSED_PORTS: &[&str] = &[EXPOSED_TCP, EXPOSED_UDP]; + +#[derive(Debug)] +pub struct DockerTestRunnerBuilder { + image: String, + host_config: HostConfig, + exposed_ports: Vec, + cmd: Option>, + entrypoint: Option>, + _server_port: u16, +} + +impl Default for DockerTestRunnerBuilder { + fn default() -> Self { + Self { + image: "hello-world".to_string(), + host_config: get_host_config(PORT), + exposed_ports: EXPOSED_PORTS + .iter() + .map(|x| x.to_string()) + .collect::>(), + cmd: None, + entrypoint: None, + _server_port: PORT, + } + } +} + +impl DockerTestRunnerBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn image(mut self, image: &str) -> Self { + self.image = image.to_string(); + self + } + + #[allow(unused)] + pub fn port(mut self, port: u16) -> Self { + self._server_port = port; + self.exposed_ports = vec![format!("{}/tcp", port), format!("{}/udp", port)]; + let mounts = self.host_config.mounts.take(); + self.host_config = get_host_config(port); + self.host_config.mounts = mounts; + + self + } + + pub fn cmd(mut self, cmd: &[&str]) -> Self { + self.cmd = Some(cmd.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 + } + + pub fn mounts(mut self, pairs: &[(&str, &str)]) -> Self { + self.host_config.mounts = Some( + pairs + .into_iter() + .map(|(src, dst)| Mount { + target: Some(dst.to_string()), + source: Some(src.to_string()), + typ: Some(bollard::secret::MountTypeEnum::BIND), + read_only: Some(true), + ..Default::default() + }) + .collect::>(), + ); + + self + } + + pub async fn build(self) -> anyhow::Result { + tracing::trace!("building docker test runner: {:?}", &self); + let exposed = self + .exposed_ports + .into_iter() + .map(|x| (x, Default::default())) + .collect::>(); + + DockerTestRunner::new( + Some(CreateImageOptions { + from_image: self.image.clone(), + ..Default::default() + }), + Config { + image: Some(self.image), + tty: Some(true), + entrypoint: self.entrypoint, + cmd: self.cmd, + exposed_ports: Some(exposed), + host_config: Some(self.host_config), + ..Default::default() + }, + ) + .await + .map_err(Into::into) + } } -pub fn default_host_config() -> HostConfig { +pub fn get_host_config(port: u16) -> HostConfig { let mut host_config = HostConfig::default(); // we need to use the host mode to enable the benchmark function #[cfg(not(target_os = "macos"))] @@ -125,17 +219,17 @@ pub fn default_host_config() -> HostConfig { host_config.port_bindings = Some( [ ( - "10002/tcp".to_owned(), + (format!("{}/tcp", port)), Some(vec![PortBinding { host_ip: Some("0.0.0.0".to_owned()), - host_port: Some("10002".to_owned()), + host_port: Some(format!("{}", port)), }]), ), ( - "10002/udp".to_owned(), + (format!("{}/udp", port)), Some(vec![PortBinding { host_ip: Some("0.0.0.0".to_owned()), - host_port: Some("10002".to_owned()), + host_port: Some(format!("{}", port)), }]), ), ] @@ -145,14 +239,3 @@ pub fn default_host_config() -> HostConfig { host_config } - -pub fn default_export_ports() -> HashMap<&'static str, HashMap<(), ()>> { - let export_ports: HashMap<&str, HashMap<(), ()>> = [ - ("10002/tcp", Default::default()), - ("10002/udp", Default::default()), - ] - .into_iter() - .collect::>(); - - export_ports -} diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index 9847c451..31b4bf5a 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -258,45 +258,25 @@ mod tests { benchmark_proxy, config_helper::test_config_base_dir, consts::*, - docker_runner::{ - default_export_ports, default_host_config, mount_config, DockerTestRunner, - }, + docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, latency_test_proxy, LatencyTestOption, }; use super::*; async fn get_ws_runner() -> anyhow::Result { - use bollard::{container::Config, image::CreateImageOptions}; - - let mut host_config = default_host_config(); let test_config_dir = test_config_base_dir(); let trojan_conf = test_config_dir.join("vmess-ws.json"); - host_config.mounts = Some(mount_config(&[( - trojan_conf.to_str().unwrap(), - "/etc/v2ray/config.json", - )])); - let export_ports = default_export_ports(); - - DockerTestRunner::new( - Some(CreateImageOptions { - from_image: IMAGE_VMESS, - ..Default::default() - }), - Config { - image: Some(IMAGE_VMESS), - tty: Some(true), - exposed_ports: Some(export_ports), - host_config: Some(host_config), - ..Default::default() - }, - ) - .await - .map_err(Into::into) + DockerTestRunnerBuilder::new() + .image(IMAGE_VMESS) + .mounts(&[(trojan_conf.to_str().unwrap(), "/etc/v2ray/config.json")]) + .build() + .await } #[tokio::test] + #[serial_test::serial] async fn test_vmess_ws() -> anyhow::Result<()> { let opts = HandlerOptions { name: "test-vmess-ws".into(), @@ -343,40 +323,24 @@ mod tests { } async fn get_grpc_runner() -> anyhow::Result { - use bollard::{container::Config, image::CreateImageOptions}; - - let mut host_config = default_host_config(); let test_config_dir = test_config_base_dir(); let conf = test_config_dir.join("vmess-grpc.json"); let cert = test_config_dir.join("example.org.pem"); let key = test_config_dir.join("example.org-key.pem"); - host_config.mounts = Some(mount_config(&[ - (conf.to_str().unwrap(), "/etc/v2ray/config.json"), - (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), - (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), - ])); - - let export_ports = default_export_ports(); - - DockerTestRunner::new( - Some(CreateImageOptions { - from_image: IMAGE_VMESS, - ..Default::default() - }), - Config { - image: Some(IMAGE_VMESS), - tty: Some(true), - exposed_ports: Some(export_ports), - host_config: Some(host_config), - ..Default::default() - }, - ) - .await - .map_err(Into::into) + DockerTestRunnerBuilder::new() + .image(IMAGE_VMESS) + .mounts(&[ + (conf.to_str().unwrap(), "/etc/v2ray/config.json"), + (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), + (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), + ]) + .build() + .await } #[tokio::test] + #[serial_test::serial] async fn test_vmess_grpc() -> anyhow::Result<()> { let opts = HandlerOptions { name: "test-vmess-grpc".into(), @@ -422,40 +386,24 @@ mod tests { } async fn get_h2_runner() -> anyhow::Result { - use bollard::{container::Config, image::CreateImageOptions}; - - let mut host_config = default_host_config(); let test_config_dir = test_config_base_dir(); let conf = test_config_dir.join("vmess-http2.json"); let cert = test_config_dir.join("example.org.pem"); let key = test_config_dir.join("example.org-key.pem"); - host_config.mounts = Some(mount_config(&[ - (conf.to_str().unwrap(), "/etc/v2ray/config.json"), - (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), - (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), - ])); - - let export_ports = default_export_ports(); - - DockerTestRunner::new( - Some(CreateImageOptions { - from_image: IMAGE_VMESS, - ..Default::default() - }), - Config { - image: Some(IMAGE_VMESS), - tty: Some(true), - exposed_ports: Some(export_ports), - host_config: Some(host_config), - ..Default::default() - }, - ) - .await - .map_err(Into::into) + DockerTestRunnerBuilder::new() + .image(IMAGE_VMESS) + .mounts(&[ + (conf.to_str().unwrap(), "/etc/v2ray/config.json"), + (cert.to_str().unwrap(), "/etc/ssl/v2ray/fullchain.pem"), + (key.to_str().unwrap(), "/etc/ssl/v2ray/privkey.pem"), + ]) + .build() + .await } #[tokio::test] + #[serial_test::serial] async fn test_vmess_h2() -> anyhow::Result<()> { let opts = HandlerOptions { name: "test-vmess-h2".into(), From 8af53f1220ae11f26c04245dfa829158c307409a Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Mon, 18 Mar 2024 23:41:57 +0800 Subject: [PATCH 10/24] move println to tracing --- clash_lib/src/proxy/utils/test_utils/mod.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index 197ca4b2..edc56c6f 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -1,11 +1,16 @@ use std::sync::Arc; +use crate::{ + app::dispatcher::ChainedStream, + proxy::OutboundHandler, + session::{Session, SocksAddr}, +}; use tokio::{ io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, join, net::TcpListener, }; -use crate::{app::dispatcher::ChainedStream, proxy::OutboundHandler, session::{Session, SocksAddr}}; +use tracing::{debug, error}; pub mod config_helper; pub mod consts; @@ -59,7 +64,7 @@ pub async fn benchmark_proxy(handler: Arc, port: u16) -> an eprintln!("Failed to accept connection: {}", e); } } - println!("server task finished"); + debug!("server task finished"); }); async fn proxy_fn(stream: Box) -> anyhow::Result<()> { @@ -86,11 +91,11 @@ pub async fn benchmark_proxy(handler: Arc, port: u16) -> an match handler.connect_stream(&sess, resolver).await { Ok(stream) => match proxy_fn(stream).await { Ok(_) => {} - Err(e) => eprintln!("Failed to to proxy: {}", e), + Err(e) => error!("Failed to to proxy: {}", e), }, - Err(e) => eprintln!("Failed to accept connection: {}", e), + Err(e) => error!("Failed to accept connection: {}", e), } - println!("proxy task finished"); + debug!("proxy task finished"); }); let _ = join!(proxy_task, target_local_server_handler); @@ -132,16 +137,16 @@ pub async fn latency_test_proxy( if option.read_exact { read_half.read_exact(&mut response).await?; - println!("response:\n{}", String::from_utf8_lossy(&response)); + debug!("response:\n{}", String::from_utf8_lossy(&response)); assert_eq!(&response, option.expected_resp); } else { read_half.read_to_end(&mut response).await?; - println!("response:\n{}", String::from_utf8_lossy(&response)); + debug!("response:\n{}", String::from_utf8_lossy(&response)); assert_eq!(&response, option.expected_resp); } let end_time = std::time::SystemTime::now(); - println!( + debug!( "time cost:{:?}", end_time.duration_since(start_time).unwrap() ); From 90a5b6e540579ecc03b9ad0c6b4d4e6963dfb4b4 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Mon, 18 Mar 2024 23:50:16 +0800 Subject: [PATCH 11/24] rename; add doc --- clash_lib/src/proxy/shadowsocks/mod.rs | 6 +++--- clash_lib/src/proxy/trojan/mod.rs | 12 +++++------ .../proxy/utils/test_utils/docker_runner.rs | 10 +++++----- clash_lib/src/proxy/utils/test_utils/mod.rs | 20 +++++++++++-------- clash_lib/src/proxy/vmess/mod.rs | 16 +++++++-------- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index d6abd04b..40a9aa52 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -294,7 +294,7 @@ mod tests { use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; use super::super::utils::test_utils::{ - benchmark_proxy, consts::*, docker_runner::DockerTestRunner, latency_test_proxy, + ping_pong_test, consts::*, docker_runner::DockerTestRunner, latency_test, LatencyTestOption, }; @@ -331,8 +331,8 @@ mod tests { watch .run_and_cleanup(async move { - benchmark_proxy(handler.clone(), 10001).await?; - latency_test_proxy( + ping_pong_test(handler.clone(), 10001).await?; + latency_test( handler, LatencyTestOption { dst: SocksAddr::Domain("google.com".to_owned(), 80), diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 2b3c8c32..44839035 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -226,11 +226,11 @@ mod tests { use std::collections::HashMap; use crate::proxy::utils::test_utils::{ - benchmark_proxy, + ping_pong_test, config_helper::test_config_base_dir, consts::*, docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, - latency_test_proxy, LatencyTestOption, + latency_test, LatencyTestOption, }; use super::*; @@ -281,8 +281,8 @@ mod tests { runner .run_and_cleanup(async move { - benchmark_proxy(handler.clone(), 10001).await?; - latency_test_proxy( + ping_pong_test(handler.clone(), 10001).await?; + latency_test( handler, LatencyTestOption { dst: SocksAddr::Domain("google.com".to_owned(), 80), @@ -340,8 +340,8 @@ mod tests { runner .run_and_cleanup(async move { - benchmark_proxy(handler.clone(), 10001).await?; - latency_test_proxy( + ping_pong_test(handler.clone(), 10001).await?; + latency_test( handler, LatencyTestOption { dst: SocksAddr::Domain("google.com".to_owned(), 80), 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 00fb60e0..4f117e4c 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -13,7 +13,7 @@ use anyhow::Result; use futures::{Future, StreamExt, TryStreamExt}; pub struct DockerTestRunner { - _instance: Docker, + instance: Docker, id: String, } @@ -39,7 +39,7 @@ impl DockerTestRunner { .id; docker.start_container::(&id, None).await?; Ok(Self { - _instance: docker, + instance: docker, id, }) } @@ -48,7 +48,7 @@ impl DockerTestRunner { pub async fn exec(&self, cmd: Vec<&str>) -> anyhow::Result<()> { // non interactive let exec = self - ._instance + .instance .create_exec( &self.id, CreateExecOptions { @@ -61,7 +61,7 @@ impl DockerTestRunner { .await? .id; if let StartExecResults::Attached { mut output, .. } = - self._instance.start_exec(&exec, None).await? + self.instance.start_exec(&exec, None).await? { while let Some(Ok(msg)) = output.next().await { print!("{msg}"); @@ -89,7 +89,7 @@ impl DockerTestRunner { // you can run the cleanup manually pub async fn cleanup(self) -> anyhow::Result<()> { let s = self - ._instance + .instance .remove_container( &self.id, Some(RemoveContainerOptions { diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index edc56c6f..2dbf9550 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -17,10 +17,9 @@ pub mod consts; pub mod docker_runner; // TODO: add the thoroughput metrics -pub async fn benchmark_proxy(handler: Arc, port: u16) -> anyhow::Result<()> { - // proxy -> proxy-server -> destination(127.0.0.1:port) +pub async fn ping_pong_test(handler: Arc, port: u16) -> anyhow::Result<()> { + // PATH: our proxy handler -> proxy-server(container) -> target local server(127.0.0.1:port) - // the destination is a local server let sess = Session { destination: ("127.0.0.1".to_owned(), port) .try_into() @@ -57,11 +56,11 @@ pub async fn benchmark_proxy(handler: Arc, port: u16) -> an match listener.accept().await { Ok((stream, _)) => match destination_fn(stream).await { Ok(_) => {} - Err(e) => eprintln!("Failed to serve: {}", e), + Err(e) => error!("Failed to serve: {}", e), }, Err(e) => { // Handle error e, log it, or ignore it - eprintln!("Failed to accept connection: {}", e); + error!("Failed to accept connection: {}", e); } } debug!("server task finished"); @@ -103,20 +102,25 @@ pub async fn benchmark_proxy(handler: Arc, port: u16) -> an Ok(()) } +/// Represents the options for a latency test. pub struct LatencyTestOption<'a> { + /// The destination address for the test. pub dst: SocksAddr, + /// The request data for the test. pub req: &'a [u8], + /// The expected response data for the test. pub expected_resp: &'a [u8], + /// Indicates whether to read the exact amount of data specified by `expected_resp`. pub read_exact: bool, } -pub async fn latency_test_proxy( +// latency test of the proxy +pub async fn latency_test( handler: Arc, option: LatencyTestOption<'_>, ) -> anyhow::Result<()> { - // proxy -> proxy-server -> destination(google.com) + // our proxy handler -> proxy-server -> destination(google.com) - // the destination is a local server let sess = Session { destination: option.dst, ..Default::default() diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index 31b4bf5a..6e59435b 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -255,11 +255,11 @@ impl OutboundHandler for Handler { mod tests { use crate::proxy::utils::test_utils::{ - benchmark_proxy, + ping_pong_test, config_helper::test_config_base_dir, consts::*, docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, - latency_test_proxy, LatencyTestOption, + latency_test, LatencyTestOption, }; use super::*; @@ -304,8 +304,8 @@ mod tests { runner .run_and_cleanup(async move { - benchmark_proxy(handler.clone(), 10001).await?; - latency_test_proxy( + ping_pong_test(handler.clone(), 10001).await?; + latency_test( handler, LatencyTestOption { dst: SocksAddr::Domain("google.com".to_owned(), 80), @@ -367,8 +367,8 @@ mod tests { runner .run_and_cleanup(async move { - benchmark_proxy(handler.clone(), 10001).await?; - latency_test_proxy( + ping_pong_test(handler.clone(), 10001).await?; + latency_test( handler, LatencyTestOption { dst: SocksAddr::Domain("example.org".to_owned(), 80), @@ -430,8 +430,8 @@ mod tests { runner .run_and_cleanup(async move { - benchmark_proxy(handler.clone(), 10001).await?; - latency_test_proxy( + ping_pong_test(handler.clone(), 10001).await?; + latency_test( handler, LatencyTestOption { dst: SocksAddr::Domain("google.com".to_owned(), 80), From 4a692b9fa178e9772983216934c5c28044148cf9 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Tue, 19 Mar 2024 11:37:57 +0800 Subject: [PATCH 12/24] simplify DockerTestRunner type signature --- .../src/proxy/utils/test_utils/docker_runner.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) 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 4f117e4c..f0efba5b 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -18,13 +18,9 @@ pub struct DockerTestRunner { } impl DockerTestRunner { - pub async fn new< - 'a, - T1: serde::ser::Serialize + Into + std::fmt::Debug + Clone, - Z: Into + std::hash::Hash + Eq + serde::Serialize, - >( - image_conf: Option>, - container_conf: Config, + pub async fn new<'a>( + image_conf: Option>, + container_conf: Config, ) -> Result { let docker: Docker = Docker::connect_with_socket_defaults()?; @@ -34,7 +30,7 @@ impl DockerTestRunner { .await?; let id = docker - .create_container::<&str, Z>(None, container_conf) + .create_container::<&str, String>(None, container_conf) .await? .id; docker.start_container::(&id, None).await?; From 675d1f8d77464bb5d8e328e9ebc0e7d172e6a6bc Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Tue, 19 Mar 2024 23:22:15 +0800 Subject: [PATCH 13/24] simplify DockerTestRunner, remove useless exec function --- .../proxy/utils/test_utils/docker_runner.rs | 31 +------------------ 1 file changed, 1 insertion(+), 30 deletions(-) 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 f0efba5b..311c0032 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -6,11 +6,10 @@ use bollard::container::{Config, RemoveContainerOptions}; use bollard::secret::{HostConfig, Mount, PortBinding}; use bollard::Docker; -use bollard::exec::{CreateExecOptions, StartExecResults}; use bollard::image::CreateImageOptions; use anyhow::Result; -use futures::{Future, StreamExt, TryStreamExt}; +use futures::{Future, TryStreamExt}; pub struct DockerTestRunner { instance: Docker, @@ -40,34 +39,6 @@ impl DockerTestRunner { }) } - #[allow(unused)] - pub async fn exec(&self, cmd: Vec<&str>) -> anyhow::Result<()> { - // non interactive - let exec = self - .instance - .create_exec( - &self.id, - CreateExecOptions { - attach_stdout: Some(true), - attach_stderr: Some(true), - cmd: Some(cmd), - ..Default::default() - }, - ) - .await? - .id; - if let StartExecResults::Attached { mut output, .. } = - self.instance.start_exec(&exec, None).await? - { - while let Some(Ok(msg)) = output.next().await { - print!("{msg}"); - } - return Ok(()); - } else { - anyhow::bail!("failed to execute cmd") - } - } - // will make sure the container is cleaned up after the future is finished pub async fn run_and_cleanup( self, From b6a4a6dee96d67d6250bb67000d508d78a1b9c02 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Tue, 19 Mar 2024 23:24:42 +0800 Subject: [PATCH 14/24] fix format error --- clash_lib/src/proxy/shadowsocks/mod.rs | 3 +-- clash_lib/src/proxy/trojan/mod.rs | 3 +-- clash_lib/src/proxy/vmess/mod.rs | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 40a9aa52..1847b3aa 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -294,8 +294,7 @@ mod tests { use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; use super::super::utils::test_utils::{ - ping_pong_test, consts::*, docker_runner::DockerTestRunner, latency_test, - LatencyTestOption, + consts::*, docker_runner::DockerTestRunner, latency_test, ping_pong_test, LatencyTestOption, }; use super::*; diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 44839035..3d0740a3 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -226,11 +226,10 @@ mod tests { use std::collections::HashMap; use crate::proxy::utils::test_utils::{ - ping_pong_test, config_helper::test_config_base_dir, consts::*, docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, - latency_test, LatencyTestOption, + latency_test, ping_pong_test, LatencyTestOption, }; use super::*; diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index 6e59435b..d2a573d9 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -255,11 +255,10 @@ impl OutboundHandler for Handler { mod tests { use crate::proxy::utils::test_utils::{ - ping_pong_test, config_helper::test_config_base_dir, consts::*, docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, - latency_test, LatencyTestOption, + latency_test, ping_pong_test, LatencyTestOption, }; use super::*; From 42558055a13990f214ec75efa32a9b5f3361f42d Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Tue, 19 Mar 2024 23:47:15 +0800 Subject: [PATCH 15/24] exclude macos from the docker test's supported archs --- clash_lib/src/proxy/shadowsocks/mod.rs | 1 + clash_lib/src/proxy/trojan/mod.rs | 1 + clash_lib/src/proxy/vmess/mod.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 1847b3aa..d5bd9e03 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -289,6 +289,7 @@ impl OutboundHandler for Handler { } #[cfg(test)] +#[cfg(not(target_os = "macos"))] mod tests { use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 3d0740a3..651edb9f 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -221,6 +221,7 @@ impl OutboundHandler for Handler { } #[cfg(test)] +#[cfg(not(target_os = "macos"))] mod tests { use std::collections::HashMap; diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index d2a573d9..538cd69c 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -252,6 +252,7 @@ impl OutboundHandler for Handler { } #[cfg(test)] +#[cfg(not(target_os = "macos"))] mod tests { use crate::proxy::utils::test_utils::{ From 7911d727a53634fc7b066892a9ee1feb5ca5a6cb Mon Sep 17 00:00:00 2001 From: dev0 Date: Thu, 21 Mar 2024 23:45:26 +1100 Subject: [PATCH 16/24] use build flag to gate tests --- .github/workflows/ci.yml | 6 +++- clash_lib/src/proxy/shadowsocks/mod.rs | 3 +- clash_lib/src/proxy/trojan/mod.rs | 3 +- clash_lib/src/proxy/utils/mod.rs | 2 +- clash_lib/src/proxy/utils/test_utils/mod.rs | 32 ++++++++++++--------- clash_lib/src/proxy/vmess/mod.rs | 3 +- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4cf49c7..6e548050 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,11 @@ jobs: run: cargo check --all --all-features - name: Run cargo clippy run: cargo clippy --all --all-features -- -D warnings - - name: Run cargo test + - name: Run cargo test on macOS + if: ${{ matrix.os }} != 'macos-13'' + run: cargo --config 'build.rustflags=["--cfg", "docker"]' test --all --all-features + - name: Run cargo test on other platforms + if: ${{ matrix.os }} == 'macos-13'' run: cargo test --all --all-features - name: Build artifacts run: sh ./scripts/build.sh "${{ matrix.platforms.target }}" "${{ matrix.static }}" diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index d5bd9e03..4747d271 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -288,8 +288,7 @@ impl OutboundHandler for Handler { } } -#[cfg(test)] -#[cfg(not(target_os = "macos"))] +#[cfg(all(test, docker))] mod tests { use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 651edb9f..a19ec265 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -220,8 +220,7 @@ impl OutboundHandler for Handler { } } -#[cfg(test)] -#[cfg(not(target_os = "macos"))] +#[cfg(all(test, docker))] mod tests { use std::collections::HashMap; diff --git a/clash_lib/src/proxy/utils/mod.rs b/clash_lib/src/proxy/utils/mod.rs index 6fbadf57..cd366f7a 100644 --- a/clash_lib/src/proxy/utils/mod.rs +++ b/clash_lib/src/proxy/utils/mod.rs @@ -3,7 +3,7 @@ use std::{ net::{IpAddr, SocketAddr}, }; -#[cfg(test)] +#[cfg(all(test, docker))] pub mod test_utils; pub mod provider_helper; diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index 2dbf9550..b104c46a 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -5,9 +5,9 @@ use crate::{ proxy::OutboundHandler, session::{Session, SocksAddr}, }; +use futures::future::join_all; use tokio::{ io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, - join, net::TcpListener, }; use tracing::{debug, error}; @@ -54,16 +54,13 @@ pub async fn ping_pong_test(handler: Arc, port: u16) -> any let target_local_server_handler = tokio::spawn(async move { match listener.accept().await { - Ok((stream, _)) => match destination_fn(stream).await { - Ok(_) => {} - Err(e) => error!("Failed to serve: {}", e), - }, + Ok((stream, _)) => destination_fn(stream).await, Err(e) => { // Handle error e, log it, or ignore it error!("Failed to accept connection: {}", e); + Err(anyhow!("Failed to accept connection: {}", e)) } } - debug!("server task finished"); }); async fn proxy_fn(stream: Box) -> anyhow::Result<()> { @@ -88,18 +85,25 @@ pub async fn ping_pong_test(handler: Arc, port: u16) -> any let proxy_task = tokio::spawn(async move { match handler.connect_stream(&sess, resolver).await { - Ok(stream) => match proxy_fn(stream).await { - Ok(_) => {} - Err(e) => error!("Failed to to proxy: {}", e), - }, - Err(e) => error!("Failed to accept connection: {}", e), + Ok(stream) => proxy_fn(stream).await, + Err(e) => { + error!("Failed to accept connection: {}", e); + Err(anyhow!("Failed to accept connection: {}", e)) + } } - debug!("proxy task finished"); }); - let _ = join!(proxy_task, target_local_server_handler); + let futs = vec![proxy_task, target_local_server_handler]; - Ok(()) + match join_all(futs) + .await + .into_iter() + .filter_map(|x| x.err()) + .next() + { + Some(e) => Err(anyhow!("Failed to run ping_pong_test: {}", e)), + None => Ok(()), + } } /// Represents the options for a latency test. diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index 538cd69c..5bb12249 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -251,8 +251,7 @@ impl OutboundHandler for Handler { } } -#[cfg(test)] -#[cfg(not(target_os = "macos"))] +#[cfg(all(test, docker))] mod tests { use crate::proxy::utils::test_utils::{ From c91162dd04a4a08b0cdcdb891bb6214233805877 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Fri, 22 Mar 2024 21:18:16 +0800 Subject: [PATCH 17/24] fix ci error; simplify the docker test code, add timeoutlogic --- .github/workflows/ci.yml | 2 +- clash_lib/build.rs | 5 ++ clash_lib/src/proxy/shadowsocks/mod.rs | 26 +------ clash_lib/src/proxy/trojan/mod.rs | 48 ++----------- clash_lib/src/proxy/utils/mod.rs | 2 +- .../src/proxy/utils/test_utils/consts.rs | 2 - .../proxy/utils/test_utils/docker_runner.rs | 12 +++- clash_lib/src/proxy/utils/test_utils/mod.rs | 41 ++++++++++- clash_lib/src/proxy/vmess/mod.rs | 70 ++----------------- 9 files changed, 67 insertions(+), 141 deletions(-) create mode 100644 clash_lib/build.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e548050..38c87898 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: run: cargo clippy --all --all-features -- -D warnings - name: Run cargo test on macOS if: ${{ matrix.os }} != 'macos-13'' - run: cargo --config 'build.rustflags=["--cfg", "docker"]' test --all --all-features + run: CLASH_RS_CI=true cargo test --all --all-features - name: Run cargo test on other platforms if: ${{ matrix.os }} == 'macos-13'' run: cargo test --all --all-features diff --git a/clash_lib/build.rs b/clash_lib/build.rs new file mode 100644 index 00000000..3194ade1 --- /dev/null +++ b/clash_lib/build.rs @@ -0,0 +1,5 @@ +fn main(){ + if std::env::var("CLASH_RS_CI").is_ok() { + println!("cargo:rustc-cfg=ci"); + } +} \ No newline at end of file diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 4747d271..ade423be 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -288,14 +288,12 @@ impl OutboundHandler for Handler { } } -#[cfg(all(test, docker))] +#[cfg(all(test, not(ci)))] mod tests { use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; - use super::super::utils::test_utils::{ - consts::*, docker_runner::DockerTestRunner, latency_test, ping_pong_test, LatencyTestOption, - }; + use super::super::utils::test_utils::{consts::*, docker_runner::DockerTestRunner, run}; use super::*; @@ -325,24 +323,6 @@ mod tests { udp: false, }; let handler = Handler::new(opts); - - let watch = get_runner().await?; - - watch - .run_and_cleanup(async move { - ping_pong_test(handler.clone(), 10001).await?; - latency_test( - handler, - LatencyTestOption { - dst: SocksAddr::Domain("google.com".to_owned(), 80), - req: GOOGLE_REQ, - expected_resp: GOOGLE_RESP_301, - read_exact: true, - }, - ) - .await?; - Ok(()) - }) - .await + run(handler, get_runner()).await } } diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index a19ec265..63c9874a 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -220,7 +220,7 @@ impl OutboundHandler for Handler { } } -#[cfg(all(test, docker))] +#[cfg(all(test, not(ci)))] mod tests { use std::collections::HashMap; @@ -229,7 +229,7 @@ mod tests { config_helper::test_config_base_dir, consts::*, docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, - latency_test, ping_pong_test, LatencyTestOption, + run, }; use super::*; @@ -275,27 +275,7 @@ mod tests { })), }; let handler = Handler::new(opts); - - let runner = get_ws_runner().await?; - - runner - .run_and_cleanup(async move { - ping_pong_test(handler.clone(), 10001).await?; - latency_test( - handler, - LatencyTestOption { - dst: SocksAddr::Domain("google.com".to_owned(), 80), - req: GOOGLE_REQ, - expected_resp: GOOGLE_RESP_301, - read_exact: true, - }, - ) - .await?; - Ok(()) - }) - .await?; - - Ok(()) + run(handler, get_ws_runner()).await } async fn get_grpc_runner() -> anyhow::Result { @@ -334,26 +314,6 @@ mod tests { })), }; let handler = Handler::new(opts); - - let runner = get_grpc_runner().await?; - - runner - .run_and_cleanup(async move { - ping_pong_test(handler.clone(), 10001).await?; - latency_test( - handler, - LatencyTestOption { - dst: SocksAddr::Domain("google.com".to_owned(), 80), - req: GOOGLE_REQ, - expected_resp: GOOGLE_RESP_301, - read_exact: true, - }, - ) - .await?; - Ok(()) - }) - .await?; - - Ok(()) + run(handler, get_grpc_runner()).await } } diff --git a/clash_lib/src/proxy/utils/mod.rs b/clash_lib/src/proxy/utils/mod.rs index cd366f7a..0c8d0002 100644 --- a/clash_lib/src/proxy/utils/mod.rs +++ b/clash_lib/src/proxy/utils/mod.rs @@ -3,7 +3,7 @@ use std::{ net::{IpAddr, SocketAddr}, }; -#[cfg(all(test, docker))] +#[cfg(all(test, not(ci)))] pub mod test_utils; pub mod provider_helper; diff --git a/clash_lib/src/proxy/utils/test_utils/consts.rs b/clash_lib/src/proxy/utils/test_utils/consts.rs index e9416cf4..600c9221 100644 --- a/clash_lib/src/proxy/utils/test_utils/consts.rs +++ b/clash_lib/src/proxy/utils/test_utils/consts.rs @@ -1,7 +1,5 @@ pub const LOCAL_ADDR: &str = "127.0.0.1"; -pub const GOOGLE_REQ: &[u8] = b"GET / HTTP/1.1\r\nHost: google.com\r\nAccept: */*\r\n\r\n"; pub const EXAMPLE_REQ: &[u8] = b"GET / HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"; -pub const GOOGLE_RESP_301: &[u8] = b"HTTP/1.1 301"; pub const EXAMLE_RESP_200: &[u8] = b"HTTP/1.1 200"; pub const IMAGE_SS_RUST: &str = "ghcr.io/shadowsocks/ssserver-rust: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 311c0032..e2492030 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -45,9 +45,17 @@ impl DockerTestRunner { f: impl Future>, ) -> anyhow::Result<()> { let fut = Box::pin(f); - let res = fut.await; + // let res = fut.await; // make sure the container is cleaned up - // TODO: select a timeout future as well, make sure it can quit smoothly + let res = tokio::select! { + res = fut => { + res + }, + _ = tokio::time::sleep(std::time::Duration::from_secs(3))=> { + tracing::warn!("timeout"); + Ok(()) + } + }; self.cleanup().await?; res diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index b104c46a..d33d5df2 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -5,18 +5,20 @@ use crate::{ proxy::OutboundHandler, session::{Session, SocksAddr}, }; -use futures::future::join_all; +use futures::{future::join_all, Future}; use tokio::{ io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::TcpListener, }; use tracing::{debug, error}; +use self::docker_runner::DockerTestRunner; + pub mod config_helper; pub mod consts; pub mod docker_runner; -// TODO: add the thoroughput metrics +// TODO: add the throughput metrics pub async fn ping_pong_test(handler: Arc, port: u16) -> anyhow::Result<()> { // PATH: our proxy handler -> proxy-server(container) -> target local server(127.0.0.1:port) @@ -102,7 +104,10 @@ pub async fn ping_pong_test(handler: Arc, port: u16) -> any .next() { Some(e) => Err(anyhow!("Failed to run ping_pong_test: {}", e)), - None => Ok(()), + None => { + tracing::info!("ping_pong_test success"); + Ok(()) + } } } @@ -160,3 +165,33 @@ pub async fn latency_test( ); Ok(()) } + +pub async fn run( + handler: Arc, + runner_creater: impl Future>, +) -> anyhow::Result<()> { + let watch = match runner_creater.await { + Ok(runner) => runner, + Err(_) => { + tracing::warn!("cannot start container, please check the docker environment"); + return Ok(()); + } + }; + + watch + .run_and_cleanup(async move { + ping_pong_test(handler.clone(), 10001).await?; + latency_test( + handler, + LatencyTestOption { + dst: SocksAddr::Domain("example.com".to_owned(), 80), + req: consts::EXAMPLE_REQ, + expected_resp: consts::EXAMLE_RESP_200, + read_exact: true, + }, + ) + .await?; + Ok(()) + }) + .await +} diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index 5bb12249..bdc561cc 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -251,14 +251,14 @@ impl OutboundHandler for Handler { } } -#[cfg(all(test, docker))] +#[cfg(all(test, not(ci)))] mod tests { use crate::proxy::utils::test_utils::{ config_helper::test_config_base_dir, consts::*, docker_runner::{DockerTestRunner, DockerTestRunnerBuilder}, - latency_test, ping_pong_test, LatencyTestOption, + run, }; use super::*; @@ -298,27 +298,7 @@ mod tests { })), }; let handler = Handler::new(opts); - - let runner = get_ws_runner().await?; - - runner - .run_and_cleanup(async move { - ping_pong_test(handler.clone(), 10001).await?; - latency_test( - handler, - LatencyTestOption { - dst: SocksAddr::Domain("google.com".to_owned(), 80), - req: GOOGLE_REQ, - expected_resp: GOOGLE_RESP_301, - read_exact: true, - }, - ) - .await?; - Ok(()) - }) - .await?; - - Ok(()) + run(handler, get_ws_runner()).await } async fn get_grpc_runner() -> anyhow::Result { @@ -361,27 +341,7 @@ mod tests { })), }; let handler = Handler::new(opts); - - let runner = get_grpc_runner().await?; - - runner - .run_and_cleanup(async move { - ping_pong_test(handler.clone(), 10001).await?; - latency_test( - handler, - LatencyTestOption { - dst: SocksAddr::Domain("example.org".to_owned(), 80), - req: EXAMPLE_REQ, - expected_resp: EXAMLE_RESP_200, - read_exact: true, - }, - ) - .await?; - Ok(()) - }) - .await?; - - Ok(()) + run(handler, get_grpc_runner()).await } async fn get_h2_runner() -> anyhow::Result { @@ -424,26 +384,6 @@ mod tests { })), }; let handler = Handler::new(opts); - - let runner = get_h2_runner().await?; - - runner - .run_and_cleanup(async move { - ping_pong_test(handler.clone(), 10001).await?; - latency_test( - handler, - LatencyTestOption { - dst: SocksAddr::Domain("google.com".to_owned(), 80), - req: GOOGLE_REQ, - expected_resp: GOOGLE_RESP_301, - read_exact: true, - }, - ) - .await?; - Ok(()) - }) - .await?; - - Ok(()) + run(handler, get_h2_runner()).await } } From 958940bdce948f7843dc3d7c137175553e2ea476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:25:09 +0800 Subject: [PATCH 18/24] Bump uuid from 1.7.0 to 1.8.0 (#331) --- Cargo.lock | 8 ++++---- clash_lib/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19a29dde..932d1d82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6789,9 +6789,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "rand", @@ -6801,9 +6801,9 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" +checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2" dependencies = [ "proc-macro2", "quote", diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index ad6a9fbc..13694f38 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -34,7 +34,7 @@ libc = "0.2" foreign-types-shared = "0.3.1" network-interface = "1.1.1" base64 = "0.22" -uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "serde"] } +uuid = { version = "1.8.0", features = ["v4", "fast-rng", "macro-diagnostics", "serde"] } boring = "4.5.0" boring-sys = "4.5.0" From 6ff61be0dca6f8dd3f26f0f35b0e3d897256973c Mon Sep 17 00:00:00 2001 From: Yuwei Ba Date: Tue, 19 Mar 2024 22:07:23 +1100 Subject: [PATCH 19/24] Update README.md Signed-off-by: Yuwei Ba --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1da861d5..35aa05df 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Can be found at https://github.com/Watfaq/clash-rs/releases Dependencies * cmake (3.29 or newer) -* Golang * libclang([LLVM](https://github.com/llvm/llvm-project/releases/tag/llvmorg-16.0.4)) * [nasm](https://www.nasm.us/pub/nasm/releasebuilds/2.16/win64/) (Windows) ``` From e03ba8cc9da843fd467eafe55395fe384ceb50e3 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Sat, 23 Mar 2024 13:50:15 +0800 Subject: [PATCH 20/24] gix style warnings --- clash_lib/build.rs | 4 ++-- clash_lib/src/proxy/utils/test_utils/docker_runner.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clash_lib/build.rs b/clash_lib/build.rs index 3194ade1..aa8dfbf6 100644 --- a/clash_lib/build.rs +++ b/clash_lib/build.rs @@ -1,5 +1,5 @@ -fn main(){ +fn main() { if std::env::var("CLASH_RS_CI").is_ok() { println!("cargo:rustc-cfg=ci"); } -} \ No newline at end of file +} 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 e2492030..0f78316a 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -1,5 +1,3 @@ -//! This example will run a non-interactive command inside the container using `docker exec` - use std::collections::HashMap; use bollard::container::{Config, RemoveContainerOptions}; @@ -11,6 +9,8 @@ use bollard::image::CreateImageOptions; use anyhow::Result; use futures::{Future, TryStreamExt}; +const TIMEOUT_DURATION: u64 = 3; + pub struct DockerTestRunner { instance: Docker, id: String, @@ -51,7 +51,7 @@ impl DockerTestRunner { res = fut => { res }, - _ = tokio::time::sleep(std::time::Duration::from_secs(3))=> { + _ = tokio::time::sleep(std::time::Duration::from_secs(TIMEOUT_DURATION))=> { tracing::warn!("timeout"); Ok(()) } From cdd187597e956e5af26d5c88e9c0c5d025adeee4 Mon Sep 17 00:00:00 2001 From: dev0 Date: Wed, 27 Mar 2024 00:29:41 +1100 Subject: [PATCH 21/24] error propgating --- Cargo.lock | 1 + clash_lib/Cargo.toml | 1 + clash_lib/src/proxy/trojan/mod.rs | 3 + .../proxy/utils/test_utils/docker_runner.rs | 5 +- clash_lib/src/proxy/utils/test_utils/mod.rs | 111 +++++++++++------- clash_lib/src/proxy/vmess/mod.rs | 9 +- 6 files changed, 82 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 932d1d82..4dc05e3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1156,6 +1156,7 @@ dependencies = [ "tracing-opentelemetry", "tracing-oslog", "tracing-subscriber", + "tracing-test", "tracing-timing", "tun", "url", diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index 13694f38..983072de 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -120,6 +120,7 @@ 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.8.0" diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 63c9874a..233b8b92 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -225,6 +225,8 @@ mod tests { use std::collections::HashMap; + use tracing_test::traced_test; + use crate::proxy::utils::test_utils::{ config_helper::test_config_base_dir, consts::*, @@ -252,6 +254,7 @@ mod tests { } #[tokio::test] + #[traced_test] #[serial_test::serial] async fn test_trojan_ws() -> anyhow::Result<()> { let opts = Opts { 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 0f78316a..ac12ffc6 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -9,7 +9,7 @@ use bollard::image::CreateImageOptions; use anyhow::Result; use futures::{Future, TryStreamExt}; -const TIMEOUT_DURATION: u64 = 3; +const TIMEOUT_DURATION: u64 = 30; pub struct DockerTestRunner { instance: Docker, @@ -53,9 +53,10 @@ impl DockerTestRunner { }, _ = tokio::time::sleep(std::time::Duration::from_secs(TIMEOUT_DURATION))=> { tracing::warn!("timeout"); - Ok(()) + Err(anyhow::anyhow!("timeout")) } }; + self.cleanup().await?; res diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index d33d5df2..d61905bb 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -1,16 +1,19 @@ -use std::sync::Arc; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use crate::{ app::dispatcher::ChainedStream, proxy::OutboundHandler, session::{Session, SocksAddr}, }; -use futures::{future::join_all, Future}; +use futures::{future::select_all, Future}; use tokio::{ io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::TcpListener, }; -use tracing::{debug, error}; +use tracing::info; use self::docker_runner::DockerTestRunner; @@ -33,6 +36,8 @@ pub async fn ping_pong_test(handler: Arc, port: u16) -> any let listener = TcpListener::bind(format!("0.0.0.0:{}", port).as_str()).await?; + info!("target local server started at: {}", listener.local_addr()?); + async fn destination_fn(incoming: T) -> anyhow::Result<()> where T: AsyncRead + AsyncWrite, @@ -42,26 +47,30 @@ pub async fn ping_pong_test(handler: Arc, port: u16) -> any let chunk = "world"; let mut buf = vec![0; 5]; + tracing::info!("destination_fn start read"); + for _ in 0..100 { read_half.read_exact(&mut buf).await?; assert_eq!(&buf, b"hello"); } + tracing::info!("destination_fn start write"); + for _ in 0..100 { write_half.write_all(chunk.as_bytes()).await?; write_half.flush().await?; } + + tracing::info!("destination_fn end"); Ok(()) } let target_local_server_handler = tokio::spawn(async move { - match listener.accept().await { - Ok((stream, _)) => destination_fn(stream).await, - Err(e) => { - // Handle error e, log it, or ignore it - error!("Failed to accept connection: {}", e); - Err(anyhow!("Failed to accept connection: {}", e)) - } + loop { + let (stream, _) = listener.accept().await?; + + tracing::info!("Accepted connection from: {}", stream.peer_addr().unwrap()); + destination_fn(stream).await? } }); @@ -71,44 +80,48 @@ pub async fn ping_pong_test(handler: Arc, port: u16) -> any let chunk = "hello"; let mut buf = vec![0; 5]; + tracing::info!("proxy_fn start write"); + for _ in 0..100 { - write_half.write_all(chunk.as_bytes()).await?; + write_half + .write_all(chunk.as_bytes()) + .await + .inspect_err(|x| { + tracing::error!("proxy_fn write error: {}", x); + })?; } write_half.flush().await?; - drop(write_half); + + tracing::info!("proxy_fn start read"); for _ in 0..100 { - read_half.read_exact(&mut buf).await?; + read_half.read_exact(&mut buf).await.inspect_err(|x| { + tracing::error!("proxy_fn read error: {}", x); + })?; assert_eq!(buf, "world".as_bytes().to_owned()); } - drop(read_half); + + tracing::info!("proxy_fn end"); + Ok(()) } let proxy_task = tokio::spawn(async move { + // give some time for the target local server to start + tokio::time::sleep(Duration::from_secs(3)).await; + match handler.connect_stream(&sess, resolver).await { Ok(stream) => proxy_fn(stream).await, Err(e) => { - error!("Failed to accept connection: {}", e); - Err(anyhow!("Failed to accept connection: {}", e)) + tracing::error!("Failed to proxy connection: {}", e); + Err(anyhow!("Failed to proxy connection: {}", e)) } } }); let futs = vec![proxy_task, target_local_server_handler]; - match join_all(futs) - .await - .into_iter() - .filter_map(|x| x.err()) - .next() - { - Some(e) => Err(anyhow!("Failed to run ping_pong_test: {}", e)), - None => { - tracing::info!("ping_pong_test success"); - Ok(()) - } - } + select_all(futs).await.0? } /// Represents the options for a latency test. @@ -127,7 +140,7 @@ pub struct LatencyTestOption<'a> { pub async fn latency_test( handler: Arc, option: LatencyTestOption<'_>, -) -> anyhow::Result<()> { +) -> anyhow::Result { // our proxy handler -> proxy-server -> destination(google.com) let sess = Session { @@ -145,25 +158,22 @@ pub async fn latency_test( write_half.flush().await?; drop(write_half); - let start_time = std::time::SystemTime::now(); + let start_time = Instant::now(); let mut response = vec![0; option.expected_resp.len()]; if option.read_exact { read_half.read_exact(&mut response).await?; - debug!("response:\n{}", String::from_utf8_lossy(&response)); + tracing::debug!("response:\n{}", String::from_utf8_lossy(&response)); assert_eq!(&response, option.expected_resp); } else { read_half.read_to_end(&mut response).await?; - debug!("response:\n{}", String::from_utf8_lossy(&response)); + tracing::debug!("response:\n{}", String::from_utf8_lossy(&response)); assert_eq!(&response, option.expected_resp); } - let end_time = std::time::SystemTime::now(); - debug!( - "time cost:{:?}", - end_time.duration_since(start_time).unwrap() - ); - Ok(()) + let end_time = Instant::now(); + tracing::debug!("time cost:{:?}", end_time.duration_since(start_time)); + Ok(end_time.duration_since(start_time)) } pub async fn run( @@ -172,16 +182,23 @@ pub async fn run( ) -> anyhow::Result<()> { let watch = match runner_creater.await { Ok(runner) => runner, - Err(_) => { + Err(e) => { tracing::warn!("cannot start container, please check the docker environment"); - return Ok(()); + return Err(e); } }; watch .run_and_cleanup(async move { - ping_pong_test(handler.clone(), 10001).await?; - latency_test( + let rv = ping_pong_test(handler.clone(), 10001).await; + if rv.is_err() { + tracing::error!("ping_pong_test failed: {:?}", rv); + return rv; + } else { + tracing::info!("ping_pong_test success"); + } + + let rv = latency_test( handler, LatencyTestOption { dst: SocksAddr::Domain("example.com".to_owned(), 80), @@ -190,8 +207,14 @@ pub async fn run( read_exact: true, }, ) - .await?; - Ok(()) + .await; + if rv.is_err() { + return Err(rv.unwrap_err()); + } else { + tracing::info!("latency test success: {}", rv.unwrap().as_millis()); + } + + return Ok(()); }) .await } diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index bdc561cc..c600a0ef 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, io, net::IpAddr, sync::Arc}; use async_trait::async_trait; use futures::TryFutureExt; +use tracing::debug; mod vmess_impl; @@ -168,6 +169,7 @@ impl OutboundHandler for Handler { sess: &Session, resolver: ThreadSafeDNSResolver, ) -> io::Result { + debug!("Connecting to {} via VMess", sess); let stream = new_tcp_stream( resolver, self.opts.server.as_str(), @@ -254,6 +256,8 @@ 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::*, @@ -265,16 +269,17 @@ mod tests { async fn get_ws_runner() -> anyhow::Result { let test_config_dir = test_config_base_dir(); - let trojan_conf = test_config_dir.join("vmess-ws.json"); + let vmess_ws_conf = test_config_dir.join("vmess-ws.json"); DockerTestRunnerBuilder::new() .image(IMAGE_VMESS) - .mounts(&[(trojan_conf.to_str().unwrap(), "/etc/v2ray/config.json")]) + .mounts(&[(vmess_ws_conf.to_str().unwrap(), "/etc/v2ray/config.json")]) .build() .await } #[tokio::test] + #[traced_test] #[serial_test::serial] async fn test_vmess_ws() -> anyhow::Result<()> { let opts = HandlerOptions { From 39d7b4cbab3ad8612391a31aa4098b297f9277b5 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Thu, 28 Mar 2024 14:02:16 +0800 Subject: [PATCH 22/24] fix env variable in github action --- .github/workflows/ci.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38c87898..69cc1fde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,12 +39,14 @@ jobs: run: cargo check --all --all-features - name: Run cargo clippy run: cargo clippy --all --all-features -- -D warnings - - name: Run cargo test on macOS - if: ${{ matrix.os }} != 'macos-13'' - run: CLASH_RS_CI=true cargo test --all --all-features - - name: Run cargo test on other platforms - if: ${{ matrix.os }} == 'macos-13'' - run: cargo test --all --all-features + - name: Run cargo test + run: | + if [ "${{ matrix.platforms.os }}" = "windows-2022" ]; then + $env:CLASH_RS_CI="true"; cargo test --all --all-features + else if [ "${{ matrix.platforms.os }}" = "ubuntu-22.04" ]; then + CLASH_RS_CI=true cargo test --all --all-features + fi + shell: bash - name: Build artifacts run: sh ./scripts/build.sh "${{ matrix.platforms.target }}" "${{ matrix.static }}" - name: Stash artifacts From ebdb61933a0cd6124464464e88d64a6653e4e788 Mon Sep 17 00:00:00 2001 From: VendettaReborn Date: Thu, 28 Mar 2024 16:07:24 +0800 Subject: [PATCH 23/24] fix ci error --- .github/workflows/ci.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69cc1fde..81b89ea1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,13 +40,14 @@ jobs: - name: Run cargo clippy run: cargo clippy --all --all-features -- -D warnings - name: Run cargo test - run: | - if [ "${{ matrix.platforms.os }}" = "windows-2022" ]; then - $env:CLASH_RS_CI="true"; cargo test --all --all-features - else if [ "${{ matrix.platforms.os }}" = "ubuntu-22.04" ]; then - CLASH_RS_CI=true cargo test --all --all-features - fi - shell: bash + if: ${{ matrix.os }} == 'windows-2022' + run: $env:CLASH_RS_CI="true"; cargo test --all --all-features + - name: Run cargo test + if: ${{ matrix.os }} == 'ubuntu-22.04' + run: CLASH_RS_CI=true cargo test --all --all-features + - name: Run cargo test on macos + if: ${{ matrix.os }} == 'macos-13' + run: cargo test --all --all-features - name: Build artifacts run: sh ./scripts/build.sh "${{ matrix.platforms.target }}" "${{ matrix.static }}" - name: Stash artifacts From 5d18edbb74b1fb33cf887b53cc3b7c3534289d4e Mon Sep 17 00:00:00 2001 From: Yuwei Ba Date: Thu, 28 Mar 2024 21:51:50 +1100 Subject: [PATCH 24/24] Update ci.yml Signed-off-by: Yuwei Ba --- .github/workflows/ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81b89ea1..d7ee4077 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,12 +39,11 @@ jobs: run: cargo check --all --all-features - name: Run cargo clippy run: cargo clippy --all --all-features -- -D warnings - - name: Run cargo test - if: ${{ matrix.os }} == 'windows-2022' - run: $env:CLASH_RS_CI="true"; cargo test --all --all-features - - name: Run cargo test - if: ${{ matrix.os }} == 'ubuntu-22.04' - run: CLASH_RS_CI=true cargo test --all --all-features + - name: Run cargo test on non-macos + if: ${{ matrix.os }} != 'macos-13' + run: cargo test --all --all-features + env: + CLASH_RS_CI: 'true' - name: Run cargo test on macos if: ${{ matrix.os }} == 'macos-13' run: cargo test --all --all-features