mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-31 19:32:13 +08:00
fix(tun): make the TUN switch reflect what the Core actually runs
The switch read enable_tun_mode while the Core was given enable_tun_mode && !session_suppression, so falling back to Sidecar left it showing a TUN that was not running, with nothing to write the flag back. Drop the session flag: the stored preference is the only truth, re-checked at startup and written off when the conditions for it are not met, and the switch renders it against current capability.
This commit is contained in:
@@ -19,11 +19,7 @@ use clash_verge_draft::Draft;
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use serde_yaml_ng::{Mapping, Value};
|
||||
use smartstring::alias::String;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
path::PathBuf,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
use tokio::sync::{Mutex, MutexGuard, OnceCell};
|
||||
use tokio::time::sleep;
|
||||
|
||||
@@ -34,7 +30,6 @@ pub(crate) struct Config {
|
||||
runtime_config: Draft<IRuntime>,
|
||||
}
|
||||
|
||||
static TUN_SESSION_SUPPRESSED: AtomicBool = AtomicBool::new(false);
|
||||
static CONFIG_WRITE_LOCK: Mutex<()> = Mutex::const_new(());
|
||||
|
||||
impl Config {
|
||||
@@ -82,24 +77,7 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tun_suppressed_for_session() -> bool {
|
||||
TUN_SESSION_SUPPRESSED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) async fn suppress_tun_for_session() {
|
||||
TUN_SESSION_SUPPRESSED.store(true, Ordering::Release);
|
||||
Handle::refresh_verge();
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn restore_tun_for_session() {
|
||||
TUN_SESSION_SUPPRESSED.store(false, Ordering::Release);
|
||||
Handle::refresh_verge();
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn disable_tun_and_persist() -> Result<()> {
|
||||
TUN_SESSION_SUPPRESSED.store(false, Ordering::Release);
|
||||
let verge = Self::verge().await;
|
||||
verge.edit_draft(|draft| {
|
||||
draft.enable_tun_mode = Some(false);
|
||||
|
||||
@@ -320,15 +320,34 @@ impl CoreManager {
|
||||
defer! {
|
||||
self.finish_config_update();
|
||||
}
|
||||
// Lock order is config then lifecycle. disable_tun_and_persist commits the shared Verge
|
||||
// draft without claiming it, so without this it can commit another transaction's staged patch.
|
||||
let config_write = Config::lock_config_write().await;
|
||||
let _life = self.lifecycle_lock.lock().await;
|
||||
let status = SERVICE_MANAGER.current().await;
|
||||
let mode = self.get_running_mode();
|
||||
if !can_allow_sidecar_for_session(&mode, &status) {
|
||||
anyhow::bail!("Sidecar continuation is not allowed from {mode:?} / {status:?}");
|
||||
}
|
||||
Config::suppress_tun_for_session().await;
|
||||
Config::generate().await?;
|
||||
SERVICE_MANAGER.allow_sidecar_for_session()?;
|
||||
// Settling on Sidecar is what makes the verdict final, so ask only once it is recorded.
|
||||
// Elevation alone carries TUN on Sidecar; a Sidecar that cannot must write it off.
|
||||
let prepared = async {
|
||||
let tun_enabled = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false);
|
||||
if crate::core::runstate::RUN_STATE
|
||||
.state()
|
||||
.tun_should_be_disabled(tun_enabled)
|
||||
{
|
||||
Config::disable_tun_and_persist().await?;
|
||||
}
|
||||
Config::generate().await
|
||||
}
|
||||
.await;
|
||||
drop(config_write);
|
||||
if let Err(error) = prepared {
|
||||
SERVICE_MANAGER.withdraw_sidecar_allowance();
|
||||
return Err(error);
|
||||
}
|
||||
// Drop the guard last so an earlier failure cannot leave a running Sidecar unguarded.
|
||||
proxy_control::stop_guard().await;
|
||||
let result = async {
|
||||
@@ -360,6 +379,8 @@ impl CoreManager {
|
||||
defer! {
|
||||
self.finish_config_update();
|
||||
}
|
||||
// Lock order is config then lifecycle, for the unclaimed draft write below.
|
||||
let _config_write = Config::lock_config_write().await;
|
||||
let _life = self.lifecycle_lock.lock().await;
|
||||
|
||||
self.controlled_stop_core_inner().await?;
|
||||
@@ -371,7 +392,14 @@ impl CoreManager {
|
||||
.await
|
||||
},
|
||||
|| async {
|
||||
Config::disable_tun_and_persist().await?;
|
||||
// Must be asked after the uninstall: until then the Service still makes TUN capable.
|
||||
let tun_enabled = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false);
|
||||
if crate::core::runstate::RUN_STATE
|
||||
.state()
|
||||
.tun_should_be_disabled(tun_enabled)
|
||||
{
|
||||
Config::disable_tun_and_persist().await?;
|
||||
}
|
||||
Config::generate().await
|
||||
},
|
||||
|| async {
|
||||
@@ -597,8 +625,7 @@ impl CoreManager {
|
||||
#[cfg(target_os = "windows")]
|
||||
self.wait_for_service_if_needed().await;
|
||||
|
||||
let service_required = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false)
|
||||
&& !Config::tun_suppressed_for_session();
|
||||
let service_required = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false);
|
||||
if service_required
|
||||
&& matches!(SERVICE_MANAGER.current().await, ServiceStatus::NotInstalled)
|
||||
&& SERVICE_MANAGER.require_install_for_session().is_err()
|
||||
@@ -645,8 +672,9 @@ impl CoreManager {
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Instant;
|
||||
|
||||
// An accepted Sidecar is the user's decision for this session, not a wait for the Service.
|
||||
let needs_service = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false)
|
||||
&& !Config::tun_suppressed_for_session();
|
||||
&& !crate::core::runstate::RUN_STATE.state().sidecar_allowed;
|
||||
if !needs_service {
|
||||
return;
|
||||
}
|
||||
@@ -701,6 +729,10 @@ impl CoreManager {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
async fn try_handoff_sidecar_to_service(&self) -> HandoffOutcome {
|
||||
// Before probing: a probe reply records an observation, which clears sidecar_allowed.
|
||||
if crate::core::runstate::RUN_STATE.state().sidecar_allowed {
|
||||
return HandoffOutcome::Done;
|
||||
}
|
||||
if !Self::refresh_service_readiness_for_handoff().await {
|
||||
return HandoffOutcome::NotReady;
|
||||
}
|
||||
@@ -717,7 +749,7 @@ impl CoreManager {
|
||||
|
||||
if !matches!(*self.get_running_mode(), RunningMode::Sidecar)
|
||||
|| !Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false)
|
||||
|| Config::tun_suppressed_for_session()
|
||||
|| crate::core::runstate::RUN_STATE.state().sidecar_allowed
|
||||
{
|
||||
return HandoffOutcome::Done;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ impl RunState {
|
||||
!matches!(self.health, ServiceHealth::Unknown) && !self.op_in_flight && !self.service_needs_attention()
|
||||
}
|
||||
|
||||
/// Present-but-broken services remain in the repair flow rather than suppressing TUN.
|
||||
/// Present-but-broken services remain in the repair flow rather than being disabled here.
|
||||
#[must_use]
|
||||
pub const fn startup_tun_should_be_disabled(&self, tun_enabled: bool) -> bool {
|
||||
matches!(self.health, ServiceHealth::NotInstalled) && self.tun_should_be_disabled(tun_enabled)
|
||||
|
||||
@@ -1327,7 +1327,6 @@ impl ServiceManager {
|
||||
RUN_STATE.perform(action).await?;
|
||||
if !matches!(action, PendingAction::Uninstall) {
|
||||
wait_for_service_ipc().await?;
|
||||
Config::restore_tun_for_session().await;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ pub struct Tray {
|
||||
|
||||
impl TrayState {
|
||||
async fn get_tray_icon(verge: &IVerge) -> (bool, Cow<'_, [u8]>) {
|
||||
let tun_mode = verge.enable_tun_mode.unwrap_or(false);
|
||||
let tun_mode = verge.enable_tun_mode.unwrap_or(false) && crate::core::runstate::RUN_STATE.state().tun_capable();
|
||||
let system_mode = verge.enable_system_proxy.unwrap_or(false);
|
||||
let kind = if tun_mode {
|
||||
IconKind::Tun
|
||||
@@ -284,7 +284,7 @@ impl Tray {
|
||||
|
||||
let verge = Config::verge().await.latest_arc();
|
||||
let system_proxy = verge.enable_system_proxy.unwrap_or(false);
|
||||
let tun_mode = verge.enable_tun_mode.unwrap_or(false);
|
||||
let tun_mode = verge.enable_tun_mode.unwrap_or(false) && crate::core::runstate::RUN_STATE.state().tun_capable();
|
||||
|
||||
let switch_str = |flag: bool| {
|
||||
if flag { "on" } else { "off" }
|
||||
|
||||
@@ -124,7 +124,7 @@ async fn get_config_values() -> ConfigValues {
|
||||
|
||||
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = (
|
||||
Some(verge_arc.get_valid_clash_core()),
|
||||
enable_tun_mode.unwrap_or(false) && !Config::tun_suppressed_for_session(),
|
||||
enable_tun_mode.unwrap_or(false),
|
||||
enable_builtin_enhanced.unwrap_or(true),
|
||||
verge_socks_enabled.unwrap_or(false),
|
||||
verge_http_enabled.unwrap_or(false),
|
||||
|
||||
+10
-13
@@ -13,13 +13,10 @@ use crate::{
|
||||
/// Prevents recursive runtime disable writes.
|
||||
static DISABLING_TUN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Suppress TUN for this Windows session when startup confirms the Service is absent.
|
||||
/// The saved preference remains available for a later Service install.
|
||||
/// Turn TUN off when startup confirms the Service is absent, so the Core can start on Sidecar.
|
||||
///
|
||||
/// Runs before the runtime config is generated.
|
||||
pub async fn reconcile_startup_tun_availability() {
|
||||
if !cfg!(target_os = "windows") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read fresh state after prior config writes complete.
|
||||
let _config_write = Config::lock_config_write().await;
|
||||
let tun_enabled = Config::verge().await.data_arc().enable_tun_mode.unwrap_or(false);
|
||||
@@ -30,9 +27,13 @@ pub async fn reconcile_startup_tun_availability() {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"Windows Service is not installed; suppressing TUN mode so the Core can start on Sidecar"
|
||||
"Service is not installed; turning TUN mode off so the Core can start on Sidecar"
|
||||
);
|
||||
Config::suppress_tun_for_session().await;
|
||||
// Write the preference only: a patch would reach update_config_checked, which with no Core to
|
||||
// reload starts one ahead of the rest of startup, and rolls the write back if that fails.
|
||||
if let Err(error) = Config::disable_tun_and_persist().await {
|
||||
logging!(error, Type::Setup, "failed to turn TUN mode off at startup: {error:#}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Disable TUN when the current settled Run State cannot support it.
|
||||
@@ -45,15 +46,11 @@ pub async fn reconcile_tun_availability() {
|
||||
let config_write = Config::lock_config_write().await;
|
||||
let state = RUN_STATE.state();
|
||||
|
||||
// Startup uses the narrower session-suppression path above.
|
||||
// Startup uses the narrower path above, before a Core is running.
|
||||
if matches!(state.mode, RunningMode::NotRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
if Config::tun_suppressed_for_session() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy writers may still hold an uncommitted draft.
|
||||
let tun_enabled = Config::verge().await.data_arc().enable_tun_mode.unwrap_or(false);
|
||||
if !state.tun_should_be_disabled(tun_enabled) {
|
||||
|
||||
@@ -196,12 +196,12 @@ const ProxyControlSwitches = ({
|
||||
{isTunMode && (
|
||||
<SwitchRow
|
||||
label={t('settings.sections.proxyControl.fields.tunMode')}
|
||||
active={enable_tun_mode || false}
|
||||
active={(enable_tun_mode && isTunModeAvailable) || false}
|
||||
infoTitle={t('settings.sections.proxyControl.tooltips.tunMode')}
|
||||
onInfoClick={() => tunRef.current?.open()}
|
||||
onToggle={handleTunToggle}
|
||||
onError={onError}
|
||||
highlight={enable_tun_mode || false}
|
||||
highlight={(enable_tun_mode && isTunModeAvailable) || false}
|
||||
extraIcons={
|
||||
<>
|
||||
{!isTunModeAvailable && (
|
||||
|
||||
Reference in New Issue
Block a user