diff --git a/v2rayN/ServiceLib.Tests/Handler/CoreConfigHandlerTests.cs b/v2rayN/ServiceLib.Tests/Handler/CoreConfigHandlerTests.cs new file mode 100644 index 00000000..e98ccadc --- /dev/null +++ b/v2rayN/ServiceLib.Tests/Handler/CoreConfigHandlerTests.cs @@ -0,0 +1,36 @@ +namespace ServiceLib.Tests.Handler; + +public class CoreConfigHandlerTests +{ + [Test] + public async Task GenerateClientConfig_CustomProfileWithMissingSource_ShouldIncludeSourcePath() + { + var sourcePath = Path.Combine(Path.GetTempPath(), $"v2rayN-missing-{Guid.NewGuid():N}.json"); + var outputPath = Path.Combine(Path.GetTempPath(), $"v2rayN-output-{Guid.NewGuid():N}.json"); + var node = new ProfileItem + { + ConfigType = EConfigType.Custom, + Address = sourcePath, + Remarks = "missing custom config", + }; + var context = new CoreConfigContext + { + Node = node, + RunCoreType = ECoreType.Xray, + }; + + try + { + var result = await CoreConfigHandler.GenerateClientConfig(context, outputPath); + + await result.Success.Should().BeFalse(); + await result.Msg.Should().Contain(ResUI.FailedReadConfiguration); + await result.Msg.Should().Contain(sourcePath); + await File.Exists(outputPath).Should().BeFalse(); + } + finally + { + File.Delete(outputPath); + } + } +} diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 9d399616..a68a9552 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -65,7 +65,9 @@ public static class CoreConfigHandler } if (!File.Exists(addressFileName)) { - ret.Msg = ResUI.FailedGenDefaultConfiguration; + var message = $"{ResUI.FailedReadConfiguration}: {addressFileName}"; + Logging.SaveLog($"{_tag}: {message}"); + ret.Msg = message; return ret; } File.Copy(addressFileName, fileName); @@ -74,7 +76,9 @@ public static class CoreConfigHandler //check again if (!File.Exists(fileName)) { - ret.Msg = ResUI.FailedGenDefaultConfiguration; + var message = $"{ResUI.FailedReadConfiguration}: {fileName}"; + Logging.SaveLog($"{_tag}: {message}"); + ret.Msg = message; return ret; } diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index b2da9c77..b1efe4c8 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -91,9 +91,19 @@ public class CoreManager await WindowsUtils.RemoveTunDevice(); } - await CoreStart(mainContext); + if (!await CoreStart(mainContext)) + { + return; + } + await WaitForProxyPort(preContext); - await CoreStartPreService(preContext); + if (!await CoreStartPreService(preContext)) + { + // Keep the ordinary proxy core available when only the optional TUN + // protection core failed. The error is reported above and the caller + // must not treat this as a successful TUN startup. + return; + } AppManager.Instance.RunningCoreType = preContext?.RunCoreType ?? mainContext.RunCoreType; @@ -176,7 +186,7 @@ public class CoreManager #region Private - private async Task CoreStart(CoreConfigContext context) + private async Task CoreStart(CoreConfigContext context) { var node = context.Node; var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); @@ -186,29 +196,45 @@ public class CoreManager var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true, context.IsTunEnabled); if (proc is null) { - return; + return false; } _processService = proc; + return true; } - private async Task CoreStartPreService(CoreConfigContext? preContext) + private async Task CoreStartPreService(CoreConfigContext? preContext) { - if (_processService is { HasExited: false } && preContext != null) + if (preContext is null) { - var preCoreType = preContext?.Node?.CoreType ?? ECoreType.sing_box; - var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(preContext, fileName); - if (result.Success) - { - var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); - var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true, preContext.IsTunEnabled); - if (proc is null) - { - return; - } - _processPreService = proc; - } + return true; } + + if (_processService is not { HasExited: false }) + { + await UpdateFunc(true, ResUI.FailedToRunCore); + return false; + } + + var preCoreType = preContext.Node?.CoreType ?? ECoreType.sing_box; + var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); + var result = await CoreConfigHandler.GenerateClientConfig(preContext, fileName); + if (!result.Success) + { + // Previously this result was ignored, so a missing/invalid + // configPre.json made Linux TUN fail without any actionable error. + await UpdateFunc(true, result.Msg ?? ResUI.FailedGenDefaultConfiguration); + return false; + } + + var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); + var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true, preContext.IsTunEnabled); + if (proc is null) + { + return false; + } + + _processPreService = proc; + return true; } private async Task UpdateFunc(bool notify, string msg) @@ -307,7 +333,7 @@ public class CoreManager var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg); if (fileName.IsNullOrEmpty()) { - await UpdateFunc(false, msg); + await UpdateFunc(mayNeedSudo, msg); return null; }