Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions clash-dns/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,14 +384,14 @@ mod tests {
use futures::FutureExt;
use hickory_net::{
client::{Client, ClientHandle},
h2::{HttpsClientStream, HttpsClientStreamBuilder},
h3::{H3ClientStream, H3ClientStreamBuilder},
h2::HttpsClientStream,
h3::H3ClientStream,
runtime::TokioRuntimeProvider,
tcp::TcpClientStream,
tls::tls_client_connect,
udp::UdpClientStream,
};
use hickory_proto::rr::{DNSClass, Name, RData, RecordType, rdata::A};
use hickory_proto::rr::{DNSClass, Name, RData, RecordType};
use rustls::{ClientConfig, pki_types::ServerName};
use std::{sync::Arc, time::Duration};
use tokio::{
Expand Down
14 changes: 10 additions & 4 deletions clash-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,34 @@ aws-lc-rs = [
"dep:aws-lc-rs",
"rustls/aws-lc-rs",
"quinn-proto/rustls-aws-lc-rs",
"quinn/rustls-aws-lc-rs",
"russh/aws-lc-rs",
"boringtun/aws-lc-rs",
"hickory-net/https-aws-lc-rs",
"tuic-quinn?/rustls-aws-lc-rs",
"watfaq-dns/aws-lc-rs",
"rcgen/aws_lc_rs",
"shadowquic?/aws-lc-rs",
]
ring = [
"rustls/ring",
"quinn-proto/ring",
"quinn/rustls-ring",
"russh/ring",
"boringtun/ring",
"hickory-net/https-ring",
"tuic-quinn?/rustls-ring",
"watfaq-dns/ring",
"rcgen/ring",
"shadowquic?/ring",
]

# Protos
shadowsocks = ["dep:shadowsocks", "dep:blake3"]
tuic = ["dep:tuic-core", "dep:register-count", "dep:tuic-quinn"]
ssh = ["dep:russh", "dep:dirs", "dep:totp-rs"]
onion = ["dep:arti-client", "dep:tor-rtcompat", "arti-client/onion-service-client"]
shadowquic = ["dep:shadowquic"]
shadowquic = ["dep:shadowquic", "shadowquic?/shadowquic-quinn", "shadowquic?/sunnyquic-iroh-quinn"]
wireguard = ["dep:boringtun", "dep:smoltcp"]
tailscale = ["dep:tailscale"]
tproxy = [
Expand Down Expand Up @@ -70,7 +76,7 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["loggin
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2"] }
rustls = { version = "0.23", default-features = false }
rustls-pemfile = "2"
rcgen = { version = "0.14", features = ["pem"] }
rcgen = { version = "0.14", default-features = false, features = ["pem"] }
webpki-roots = "1.0"

# Error handing & logging
Expand Down Expand Up @@ -183,7 +189,7 @@ register-count = { version = "0.1", optional = true }
# tuic-quinn gives tuic-specific quinn fork with rustls support (same fork as tuic-core uses internally)
tuic-quinn = { package = "quinn", branch = "bbrv3", git = "https://github.com/Tipuch/quinn.git", optional = true, default-features = false, features = ["runtime-tokio"] }

quinn = { version = "0.11", default-features = false, features = ["futures-io", "runtime-tokio", "rustls"] }
quinn = { version = "0.11", default-features = false, features = ["futures-io", "runtime-tokio"] }

# hysteria2
h3 = "0.0.8"
Expand All @@ -202,7 +208,7 @@ dirs = { version = "6.0", optional = true }
totp-rs = { version = "^5.7", features = ["serde_support"], optional = true }

# shadowquic
shadowquic = { version = "0.3.4", optional = true, git = "https://github.com/spongebob888/shadowquic.git", rev = "277f47a59607a91d11bcb4ae98309a320617d4b2" }
shadowquic = { version = "0.3.4", optional = true, default-features = false, git = "https://github.com/ibigbug/shadowquic.git", rev = "a910118d5e3ab53e19e2b11347c5abaa93347ce6" }

# experimental
downcast-rs = "2.0"
Expand Down
54 changes: 29 additions & 25 deletions clash-lib/tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unused_imports)]

use clash_lib::{Config, Options};
use common::{Socks5UdpSession, start_clash, wait_port_ready};
use common::{ClashInstance, Socks5UdpSession, start_clash, wait_port_ready};
use std::path::PathBuf;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

Expand Down Expand Up @@ -133,34 +133,36 @@ async fn integration_test_anytls() {
client_config.to_string_lossy()
);

std::thread::spawn(move || {
start_clash(Options {
// Use ClashInstance so server/client are properly shut down when the test
// ends, releasing ports 8902/9092 and 8998/9095 for subsequent tests.
let _server = ClashInstance::start(
Options {
config: Config::File(server_config.to_string_lossy().to_string()),
cwd: Some(wd_server.to_string_lossy().to_string()),
rt: None,
log_file: None,
})
.expect("Failed to start AnyTLS server");
});
},
vec![8902, 9092],
)
.expect("Failed to start AnyTLS server");

std::thread::spawn(move || {
start_clash(Options {
let _client = ClashInstance::start(
Options {
config: Config::File(client_config.to_string_lossy().to_string()),
cwd: Some(wd_client.to_string_lossy().to_string()),
rt: None,
log_file: None,
})
.expect("Failed to start AnyTLS client");
});
},
vec![8998, 9095],
)
.expect("Failed to start AnyTLS client");
Comment on lines +138 to +158

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether aws-lc provider install is present before AnyTLS starts in this test file.
rg -n --type rust -C2 'install_default|integration_test_anytls|integration_test_anytls_udp|ClashInstance::start' clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 1635


🏁 Script executed:

# Check entire integration_tests.rs file for any TLS provider installations
rg -n "install_default|rustls::crypto" clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 41


🏁 Script executed:

# Count total lines and show the overall structure of test functions in the file
wc -l clash-lib/tests/integration_tests.rs && echo "---" && rg -n "^async fn integration_test" clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 335


🏁 Script executed:

# Show lines around 117 (start of integration_test_anytls) through 200 to see the full first test
sed -n '117,200p' clash-lib/tests/integration_tests.rs | head -50

Repository: Watfaq/clash-rs

Length of output: 1846


🏁 Script executed:

# Search for module-level setup functions, once patterns, or feature gates
rg -n "static|Once|#\[cfg" clash-lib/tests/integration_tests.rs | head -20

Repository: Watfaq/clash-rs

Length of output: 94


🏁 Script executed:

# Get lines 1-50 to see file header, imports, and any setup
head -50 clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 1700


🏁 Script executed:

# Check if any other test functions also use ClashInstance with AnyTLS configs
rg -n "anytls|ClashInstance::start" clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 626


Install the AWS-LC rustls provider before AnyTLS startup in these tests.

ClashInstance::start(...) on line 138 and line 223 enter TLS code paths; without installing the aws-lc provider first, these tests can fail/flap under aws-lc-rs feature runs.

Per coding guidelines (**/*test*.rs), rustls::crypto::aws_lc_rs::default_provider().install_default() must be called before constructing TLS configs in tests. Add a module-level helper function with std::sync::Once to ensure single initialization across both affected test functions:

Suggested patch
+fn install_tls_provider_for_tests() {
+    #[cfg(feature = "aws-lc-rs")]
+    {
+        static INIT: std::sync::Once = std::sync::Once::new();
+        INIT.call_once(|| {
+            let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
+        });
+    }
+}
+
 #[tokio::test(flavor = "current_thread")]
 #[serial_test::serial]
 /// Test AnyTLS inbound and outbound functionality (ephemeral self-signed cert)
 async fn integration_test_anytls() {
+    install_tls_provider_for_tests();
     let wd_server =
         PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/config/server");
@@
 #[tokio::test(flavor = "current_thread")]
 #[serial_test::serial]
 /// Test AnyTLS UDP-over-TCP v2: send a UDP datagram through SOCKS5 UDP
 /// ASSOCIATE → AnyTLS inbound → echo server, verify round-trip payload.
 async fn integration_test_anytls_udp() {
+    install_tls_provider_for_tests();
     use std::net::SocketAddr;
     use tokio::net::UdpSocket;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@clash-lib/tests/integration_tests.rs` around lines 138 - 158, Tests call
ClashInstance::start which triggers TLS setup and can race under aws-lc-rs; add
a module-level initializer that calls
rustls::crypto::aws_lc_rs::default_provider().install_default() once (use
std::sync::Once) and invoke it at the start of affected tests (before calling
ClashInstance::start in integration_tests.rs) so the AWS-LC provider is
installed exactly once across tests.


let mock_server = httpmock::MockServer::start();
let mock = mock_server.mock(|when, then| {
when.method(httpmock::Method::GET).path("/");
then.status(200).body("Mock response for AnyTLS testing");
});

wait_port_ready(8998).expect("AnyTLS proxy port is not ready");

let proxy = reqwest::Proxy::all("socks5://127.0.0.1:8998")
.expect("Failed to create proxy");

Expand Down Expand Up @@ -216,27 +218,29 @@ async fn integration_test_anytls_udp() {
});

// ── Start clash server and client ─────────────────────────────────────────
std::thread::spawn(move || {
start_clash(Options {
// Use ClashInstance so server/client are properly shut down when the test
// ends, releasing ports 8902/9092 and 8998/9095 for subsequent tests.
let _server = ClashInstance::start(
Options {
config: Config::File(server_config.to_string_lossy().to_string()),
cwd: Some(wd_server.to_string_lossy().to_string()),
rt: None,
log_file: None,
})
.expect("Failed to start AnyTLS server");
});
},
vec![8902, 9092],
)
.expect("Failed to start AnyTLS server");

std::thread::spawn(move || {
start_clash(Options {
let _client = ClashInstance::start(
Options {
config: Config::File(client_config.to_string_lossy().to_string()),
cwd: Some(wd_client.to_string_lossy().to_string()),
rt: None,
log_file: None,
})
.expect("Failed to start AnyTLS client");
});

wait_port_ready(8998).expect("AnyTLS proxy port is not ready");
},
vec![8998, 9095],
)
.expect("Failed to start AnyTLS client");

// ── SOCKS5 UDP ASSOCIATE handshake ────────────────────────────────────────
let mut tcp = tokio::net::TcpStream::connect("127.0.0.1:8998")
Expand Down
Loading