From ad0035edb9ba0f5b0cce330a2f546d6dee8a3be9 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:50:22 +0800 Subject: [PATCH] refactor(net): reuse webpki tls config and fallback Extract build_webpki_rooted_tls_config so the delay-test TLS connector and the reqwest clients share one static-webpki-roots builder; ALPN stays caller-specific (only reqwest offers h2). Collapse the duplicated platform-verifier -> static-webpki-roots retry in get/get_bytes into request_with_static_webpki_fallback, gated by the caller's certificate-verification setting. --- src-tauri/src/feat/clash.rs | 11 +---- src-tauri/src/utils/network.rs | 82 +++++++++++++++++----------------- 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index 0f90dffc4..beddd2dec 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -14,15 +14,8 @@ use smartstring::alias::String; use std::sync::{Arc, LazyLock}; #[allow(clippy::expect_used)] -static TLS_CONFIG: LazyLock> = LazyLock::new(|| { - 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() - .expect("Failed to set TLS versions") - .with_root_certificates(root_store) - .with_no_client_auth(); - Arc::new(config) -}); +static TLS_CONFIG: LazyLock> = + LazyLock::new(|| Arc::new(utils::network::build_webpki_rooted_tls_config().expect("Failed to set TLS versions"))); pub async fn restart_clash_core() { match CoreManager::global().restart_core().await { diff --git a/src-tauri/src/utils/network.rs b/src-tauri/src/utils/network.rs index d325fecbd..89d2ddc6f 100644 --- a/src-tauri/src/utils/network.rs +++ b/src-tauri/src/utils/network.rs @@ -48,6 +48,17 @@ enum TlsRootMode { StaticWebpkiRoots, } +/// ALPN stays unset: raw TLS connectors offer none, while reqwest needs the h2 offer. +pub(crate) fn build_webpki_rooted_tls_config() -> Result { + 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(); + + Ok(config) +} + pub struct NetworkManager; impl NetworkManager { @@ -102,13 +113,7 @@ impl NetworkManager { } fn build_static_webpki_tls_config() -> Result { - let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let mut 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 mut config = build_webpki_rooted_tls_config()?; config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; Ok(config) @@ -155,6 +160,23 @@ impl NetworkManager { detail.contains("protocolversion") || detail.contains("protocol version") } + /// `allow_fallback` stays false when the caller already disabled certificate verification. + async fn request_with_static_webpki_fallback(allow_fallback: bool, mut request: F) -> Result + where + F: FnMut(TlsRootMode) -> Fut, + Fut: std::future::Future>, + { + match request(TlsRootMode::PlatformVerifier).await { + Ok(value) => Ok(value), + Err(err) if allow_fallback && Self::should_retry_with_static_webpki_roots(&err) => { + request(TlsRootMode::StaticWebpkiRoots).await.map_err(|fallback_err| { + fallback_err.context("static webpki roots fallback failed after platform TLS verifier failed") + }) + } + Err(err) => Err(err), + } + } + async fn get_with_tls_mode( &self, url: &str, @@ -264,34 +286,21 @@ impl NetworkManager { user_agent: Option, accept_invalid_certs: bool, ) -> Result { - let platform_result = self - .get_with_tls_mode( - url, - proxy_type, - timeout_secs, - user_agent.clone(), - accept_invalid_certs, - TlsRootMode::PlatformVerifier, - ) - .await; - - match platform_result { - Ok(response) => Ok(response), - Err(err) if !accept_invalid_certs && Self::should_retry_with_static_webpki_roots(&err) => self - .get_with_tls_mode( + Self::request_with_static_webpki_fallback(!accept_invalid_certs, |tls_root_mode| { + let user_agent = user_agent.clone(); + async move { + self.get_with_tls_mode( url, proxy_type, timeout_secs, user_agent, accept_invalid_certs, - TlsRootMode::StaticWebpkiRoots, + tls_root_mode, ) .await - .map_err(|fallback_err| { - fallback_err.context("static webpki roots fallback failed after platform TLS verifier failed") - }), - Err(err) => Err(err), - } + } + }) + .await } async fn get_bytes_with_tls_mode( @@ -340,19 +349,10 @@ impl NetworkManager { timeout_secs: Option, max_bytes: usize, ) -> Result> { - let platform_result = self - .get_bytes_with_tls_mode(url, proxy_type, timeout_secs, max_bytes, TlsRootMode::PlatformVerifier) - .await; - - match platform_result { - Ok(body) => Ok(body), - Err(err) if Self::should_retry_with_static_webpki_roots(&err) => self - .get_bytes_with_tls_mode(url, proxy_type, timeout_secs, max_bytes, TlsRootMode::StaticWebpkiRoots) + Self::request_with_static_webpki_fallback(true, |tls_root_mode| async move { + self.get_bytes_with_tls_mode(url, proxy_type, timeout_secs, max_bytes, tls_root_mode) .await - .map_err(|fallback_err| { - fallback_err.context("static webpki roots fallback failed after platform TLS verifier failed") - }), - Err(err) => Err(err), - } + }) + .await } }