From 02df430172c72ae464459b10c97f4d0d7e5dcc01 Mon Sep 17 00:00:00 2001 From: hyjhyj111 <165823038+hyjhyj111@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:13:28 +0800 Subject: [PATCH] Use the TUN context snapshot in remaining launch-path reads (#9831) Follow-up to #9830: audit of all TunModeItem.EnableTun usages found three more launch-path reads of the live mutable config where the behavior must agree with the context snapshot that generated the config: - CoreConfigClashService (mihomo custom config): the tun section was decided from the live config while the mihomo launch elevation uses the snapshot; a mid-reload toggle could produce a config containing tun launched without sudo, the same failure fixed in #9830. The tun state is now passed in as a snapshot. - CoreManager.LoadCore: the Windows RemoveTunDevice cleanup now checks the main/pre context snapshots. - CoreManager.WaitForProxyPort: preContext.AppConfig is a shared live reference; use preContext.IsTunEnabled instead. Reads that intentionally stay live: StatusBarViewModel (UI state source), CoreConfigContextBuilder (the snapshot capture point), GetPreSocksItem (called during snapshot construction, self-consistent), and AppManager.StatePort2 (transient mid-reload skew only, self-heals after reload). --- v2rayN/ServiceLib/Handler/CoreConfigHandler.cs | 2 +- v2rayN/ServiceLib/Manager/CoreManager.cs | 4 ++-- .../Services/CoreConfig/CoreConfigClashService.cs | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 99f65662..9d399616 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -17,7 +17,7 @@ public static class CoreConfigHandler { result = node.CoreType switch { - ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), + ECoreType.mihomo => await new CoreConfigClashService(config, context.IsTunEnabled).GenerateClientCustomConfig(node, fileName), _ => await GenerateClientCustomConfig(node, fileName) }; } diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index 04ac4f3c..5e1527df 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -85,7 +85,7 @@ public class CoreManager await CoreStop(); await Task.Delay(100); - if (Utils.IsWindows() && _config.TunModeItem.EnableTun) + if (Utils.IsWindows() && (mainContext?.IsTunEnabled == true || preContext?.IsTunEnabled == true)) { await Task.Delay(100); await WindowsUtils.RemoveTunDevice(); @@ -222,7 +222,7 @@ public class CoreManager { return; } - if (!preContext.AppConfig.TunModeItem.EnableTun) + if (!preContext.IsTunEnabled) { return; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs index 0f5887a8..ce7e58e3 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs @@ -1,9 +1,11 @@ namespace ServiceLib.Services.CoreConfig; /// -/// Core configuration file processing class +/// Core configuration file processing class. +/// The TUN state is taken as a snapshot so the generated config always agrees +/// with the launch elevation decision (see CoreManager.ShouldRunAsSudo). /// -public class CoreConfigClashService(Config config) +public class CoreConfigClashService(Config config, bool isTunEnabled) { private static readonly string _tag = "CoreConfigClashService"; @@ -102,7 +104,7 @@ public class CoreConfigClashService(Config config) } //enable tun mode - if (config.TunModeItem.EnableTun) + if (isTunEnabled) { var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml); if (tun.IsNotEmpty()) @@ -171,7 +173,7 @@ public class CoreConfigClashService(Config config) } foreach (var item in mixinContent) { - if (!config.TunModeItem.EnableTun && item.Key == "tun") + if (!isTunEnabled && item.Key == "tun") { continue; }