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:
wonfen
2026-08-31 17:09:36 +08:00
parent 8611fdc34f
commit e60285b4ae
8 changed files with 56 additions and 50 deletions
+39 -7
View File
@@ -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;
}
+1 -1
View File
@@ -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)
-1
View File
@@ -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(())
})
+2 -2
View File
@@ -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" }