mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-29 10:02:09 +08:00
181 lines
6.3 KiB
Rust
181 lines
6.3 KiB
Rust
use crate::core::notify::NoticeStatus;
|
|
use crate::core::notify::{Refresh, announce};
|
|
use crate::{
|
|
config::{Config, MixedPort},
|
|
core::{CoreManager, handle, tray},
|
|
feat::clean_async,
|
|
process::AsyncHandler,
|
|
utils,
|
|
};
|
|
use bytes::BytesMut;
|
|
use clash_verge_logging::{Type, logging};
|
|
use once_cell::sync::Lazy;
|
|
use serde_yaml_ng::{Mapping, Value};
|
|
use smartstring::alias::String;
|
|
use std::sync::Arc;
|
|
|
|
#[allow(clippy::expect_used)]
|
|
static TLS_CONFIG: Lazy<Arc<rustls::ClientConfig>> = Lazy::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)
|
|
});
|
|
|
|
pub async fn restart_clash_core() {
|
|
match CoreManager::global().restart_core().await {
|
|
Ok(_) => {
|
|
announce(Refresh::Clash);
|
|
handle::Handle::notice(NoticeStatus::SetConfigOk, "ok");
|
|
}
|
|
Err(err) => {
|
|
handle::Handle::notice(NoticeStatus::SetConfigError, format!("{err:#}"));
|
|
logging!(error, Type::Core, "restart core failed: {err:#}");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn restart_app() {
|
|
logging!(debug, Type::System, "启动重启应用流程");
|
|
handle::Handle::global().set_is_exiting();
|
|
|
|
Config::apply_all_and_save_file().await;
|
|
|
|
let cleanup_result = clean_async().await;
|
|
|
|
logging!(
|
|
info,
|
|
Type::System,
|
|
"资源清理完成,退出代码: {}",
|
|
if cleanup_result.all_success { 0 } else { 1 }
|
|
);
|
|
|
|
if !cleanup_result.core_stopped {
|
|
handle::Handle::global().clear_is_exiting();
|
|
handle::Handle::notice(
|
|
NoticeStatus::AppRestartCoreStopFailed,
|
|
cleanup_result.stop_error.unwrap_or_default(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
utils::server::shutdown_embedded_server();
|
|
let app_handle = handle::Handle::app_handle();
|
|
app_handle.restart();
|
|
}
|
|
|
|
fn after_change_clash_mode() {
|
|
AsyncHandler::spawn(move || async {
|
|
if let Err(err) = handle::Handle::mihomo().close_all_connections().await {
|
|
logging!(
|
|
error,
|
|
Type::Core,
|
|
"Failed to close all connections after changing clash mode: {err}"
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Propagates mihomo PATCH failures so the frontend can roll back its optimistic mode.
|
|
pub async fn change_clash_mode(mode: String) -> Result<(), String> {
|
|
let mut mapping = Mapping::new();
|
|
mapping.insert(Value::from("mode"), Value::from(mode.as_str()));
|
|
let json_value = serde_json::json!({
|
|
"mode": mode
|
|
});
|
|
if let Err(err) = handle::Handle::mihomo().patch_base_config(&json_value).await {
|
|
logging!(error, Type::Core, "change clash mode failed: {err}");
|
|
return Err(err.to_string().into());
|
|
}
|
|
|
|
let clash = Config::clash().await;
|
|
clash.edit_draft(|d| d.patch_config(&mapping));
|
|
clash.apply();
|
|
|
|
let clash_data = clash.data_arc();
|
|
if clash_data.save_config().await.is_ok() {
|
|
announce(Refresh::Clash);
|
|
tray::Tray::global().update_menu_and_icon().await;
|
|
}
|
|
|
|
let is_auto_close_connection = Config::verge().await.data_arc().auto_close_connection.unwrap_or(false);
|
|
if is_auto_close_connection {
|
|
after_change_clash_mode();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test delay to a URL through proxy.
|
|
/// HTTPS: measures TLS handshake time. HTTP: measures HEAD round-trip time.
|
|
#[tracing::instrument(skip_all, level = "trace", fields(url = %url))]
|
|
pub async fn test_delay(url: String) -> anyhow::Result<u32> {
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
|
|
use tokio::net::TcpStream;
|
|
use tokio::time::Instant;
|
|
|
|
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 });
|
|
|
|
let verge = Config::verge().await.latest_arc();
|
|
let proxy_enabled = verge.enable_system_proxy.unwrap_or(false) || verge.enable_tun_mode.unwrap_or(false);
|
|
let proxy_port = if proxy_enabled {
|
|
Some(MixedPort::desired().await)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
tokio::time::timeout(Duration::from_secs(10), async {
|
|
let start = Instant::now();
|
|
let mut buf = BytesMut::with_capacity(1024);
|
|
|
|
if is_https {
|
|
let stream = match proxy_port {
|
|
Some(pp) => {
|
|
let mut s = TcpStream::connect(format!("127.0.0.1:{pp}")).await?;
|
|
s.write_all(format!("CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n\r\n").as_bytes())
|
|
.await?;
|
|
s.read_buf(&mut buf).await?;
|
|
if !buf.windows(3).any(|w| w == b"200") {
|
|
return Err(anyhow::anyhow!("Proxy CONNECT failed"));
|
|
}
|
|
s
|
|
}
|
|
None => TcpStream::connect(format!("{host}:{port}")).await?,
|
|
};
|
|
let connector = tokio_rustls::TlsConnector::from(Arc::clone(&TLS_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?;
|
|
} else {
|
|
let (mut stream, req) = match proxy_port {
|
|
Some(pp) => (
|
|
TcpStream::connect(format!("127.0.0.1:{pp}")).await?,
|
|
format!("HEAD {url} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"),
|
|
),
|
|
None => (
|
|
TcpStream::connect(format!("{host}:{port}")).await?,
|
|
format!("HEAD / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"),
|
|
),
|
|
};
|
|
stream.write_all(req.as_bytes()).await?;
|
|
let _ = stream.read(&mut buf).await?;
|
|
}
|
|
|
|
Ok((start.elapsed().as_millis() as u32).max(1))
|
|
})
|
|
.await
|
|
.unwrap_or(Ok(10000u32))
|
|
}
|