feat: use real TLS latency for website testing

This commit is contained in:
wonfen 2026-03-17 02:33:28 +08:00
parent 4ceb7e6043
commit 7ae3b7b0de
No known key found for this signature in database
GPG Key ID: CEAFD6C73AB2001F
3 changed files with 65 additions and 29 deletions

4
Cargo.lock generated
View File

@ -1165,6 +1165,7 @@ dependencies = [
"reqwest_dav", "reqwest_dav",
"runas", "runas",
"rust_iso3166", "rust_iso3166",
"rustls",
"scopeguard", "scopeguard",
"serde", "serde",
"serde_json", "serde_json",
@ -1189,8 +1190,10 @@ dependencies = [
"tauri-plugin-updater", "tauri-plugin-updater",
"tauri-plugin-window-state", "tauri-plugin-window-state",
"tokio", "tokio",
"tokio-rustls",
"tokio-stream", "tokio-stream",
"warp", "warp",
"webpki-roots",
"windows 0.62.2", "windows 0.62.2",
"winreg 0.56.0", "winreg 0.56.0",
"zip 8.2.0", "zip 8.2.0",
@ -6315,6 +6318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"log",
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",

View File

@ -102,6 +102,9 @@ clash_verge_service_ipc = { version = "2.1.3", features = [
"client", "client",
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" } ], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
arc-swap = "1.8.2" arc-swap = "1.8.2"
tokio-rustls = "0.26"
rustls = { version = "0.23", features = ["ring"] }
webpki-roots = "1.0"
rust_iso3166 = "0.1.14" rust_iso3166 = "0.1.14"
# Use the git repo until the next release after v2.0.0. # Use the git repo until the next release after v2.0.0.
dark-light = { git = "https://github.com/rust-dark-light/dark-light" } dark-light = { git = "https://github.com/rust-dark-light/dark-light" }

View File

@ -102,40 +102,69 @@ pub async fn change_clash_mode(mode: String) {
} }
} }
/// Test connection delay to a URL /// Test TLS handshake delay to a URL (through proxy when not in TUN mode)
pub async fn test_delay(url: String) -> anyhow::Result<u32> { pub async fn test_delay(url: String) -> anyhow::Result<u32> {
use crate::utils::network::{NetworkManager, ProxyType}; use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
use tokio::net::TcpStream;
use tokio::time::Instant; use tokio::time::Instant;
let tun_mode = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false); let parsed = tauri::Url::parse(&url)?;
let is_https = parsed.scheme() == "https";
let host = parsed
.host_str()
.ok_or_else(|| anyhow::anyhow!("Invalid URL: no host"))?
.to_string();
let port = parsed.port().unwrap_or(if is_https { 443 } else { 80 });
// 如果是TUN模式不使用代理否则使用自身代理 let verge = Config::verge().await.latest_arc();
let proxy_type = if !tun_mode { let tun_mode = verge.enable_tun_mode.unwrap_or(false);
ProxyType::Localhost
} else { let inner = async {
ProxyType::None let start = Instant::now();
// TCP connect: direct in TUN mode, via CONNECT tunnel otherwise
let stream = if tun_mode {
TcpStream::connect(format!("{host}:{port}")).await?
} else {
let proxy_port = match verge.verge_mixed_port {
Some(p) => p,
None => Config::clash().await.data_arc().get_mixed_port(),
};
let mut stream = TcpStream::connect(format!("127.0.0.1:{proxy_port}")).await?;
stream
.write_all(format!("CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n\r\n").as_bytes())
.await?;
let mut buf = [0u8; 1024];
let n = stream.read(&mut buf).await?;
if !std::str::from_utf8(&buf[..n]).unwrap_or("").contains("200") {
return Err(anyhow::anyhow!("Proxy CONNECT failed"));
}
stream
};
// TLS handshake for HTTPS; for HTTP the TCP connect above is the result
if is_https {
let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config =
rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
.with_safe_default_protocol_versions()?
.with_root_certificates(root_store)
.with_no_client_auth();
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
let server_name = rustls::pki_types::ServerName::try_from(host.as_str())
.map_err(|_| anyhow::anyhow!("Invalid DNS name: {host}"))?
.to_owned();
connector.connect(server_name, stream).await?;
}
// Ensure at least 1ms — frontend treats 0 as timeout
Ok((start.elapsed().as_millis() as u32).max(1))
}; };
let user_agent = Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0".into()); match tokio::time::timeout(Duration::from_secs(10), inner).await {
Ok(result) => result,
let start = Instant::now(); Err(_) => Ok(10000u32),
let response = NetworkManager::new()
.get_with_interrupt(&url, proxy_type, Some(10), user_agent, false)
.await;
match response {
Ok(response) => {
logging!(trace, Type::Network, "test_delay response: {response:#?}");
if response.status().is_success() {
Ok(start.elapsed().as_millis() as u32)
} else {
Ok(10000u32)
}
}
Err(err) => {
logging!(trace, Type::Network, "test_delay error: {err:#?}");
Err(err)
}
} }
} }