From b9452b2a6a6260b2cf0fe4f969cefafc9a6f8f8b Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 8 Feb 2026 20:26:46 +0800 Subject: [PATCH] Tun protect --- .../ServiceLib/Handler/CoreConfigHandler.cs | 3 +- v2rayN/ServiceLib/Manager/CoreManager.cs | 65 +++++++---- v2rayN/ServiceLib/Models/CoreConfigContext.cs | 6 +- .../Singbox/CoreConfigSingboxService.cs | 45 ++++++++ .../Singbox/SingboxInboundService.cs | 9 +- .../V2ray/CoreConfigV2rayService.cs | 105 ++++++++++++++++++ 6 files changed, 206 insertions(+), 27 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index cebf2d3f..359aab3b 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -154,7 +154,8 @@ public static class CoreConfigHandler IsTunEnabled = config.TunModeItem.EnableTun, SimpleDnsItem = config.SimpleDNSItem, ProtectDomainList = [], - ProtectSocksPort = 0, + TunProtectSsPort = 0, + ProxyRelaySsPort = 0, RawDnsItem = await AppManager.Instance.GetDNSItem(coreType), RoutingItem = await ConfigHandler.GetDefaultRouting(config), }; diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index 86934c45..0e4c33d6 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -67,7 +67,38 @@ public class CoreManager var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node); - context = context with { IsTunEnabled = _config.TunModeItem.EnableTun }; + CoreConfigContext? preContext = null; + if (context.IsTunEnabled) + { + var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); + if (coreType == ECoreType.Xray && node.ConfigType != EConfigType.Custom) + { + var tunProtectSsPort = Utils.GetFreePort(); + var proxyRelaySsPort = Utils.GetFreePort(); + context = context with { TunProtectSsPort = tunProtectSsPort, ProxyRelaySsPort = proxyRelaySsPort, }; + var preItem = new ProfileItem() + { + CoreType = ECoreType.sing_box, + ConfigType = EConfigType.Shadowsocks, + Address = Global.Loopback, + Port = proxyRelaySsPort, + Password = Global.None, + }; + preItem.SetProtocolExtra(preItem.GetProtocolExtra() with + { + SsMethod = Global.None, + }); + preContext = context with { Node = preItem, }; + } + else + { + var preItem = ConfigHandler.GetPreSocksItem(_config, node, coreType); + if (preItem is not null) + { + preContext = context with { Node = preItem, }; + } + } + } var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); if (result.Success != true) { @@ -88,7 +119,7 @@ public class CoreManager } await CoreStart(context); - await CoreStartPreService(context); + await CoreStartPreService(preContext); if (_processService != null) { await UpdateFunc(true, $"{node.GetSummary()}"); @@ -183,30 +214,22 @@ public class CoreManager _processService = proc; } - private async Task CoreStartPreService(CoreConfigContext context) + private async Task CoreStartPreService(CoreConfigContext? preContext) { - var node = context.Node; - if (_processService != null && !_processService.HasExited) + if (_processService is { HasExited: false } && preContext != null) { - var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); - var itemSocks = ConfigHandler.GetPreSocksItem(_config, node, coreType); - if (itemSocks != 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 preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; - var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); - var itemSocksContext = await CoreConfigHandler.BuildCoreConfigContext(_config, itemSocks); - itemSocksContext.ProtectDomainList.AddRangeSafe(context.ProtectDomainList); - var result = await CoreConfigHandler.GenerateClientConfig(itemSocksContext, fileName); - if (result.Success) + var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); + var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); + if (proc is null) { - var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); - var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); - if (proc is null) - { - return; - } - _processPreService = proc; + return; } + _processPreService = proc; } } } diff --git a/v2rayN/ServiceLib/Models/CoreConfigContext.cs b/v2rayN/ServiceLib/Models/CoreConfigContext.cs index 9551d39d..59e9537e 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigContext.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigContext.cs @@ -13,5 +13,9 @@ public record CoreConfigContext // TUN Compatibility public bool IsTunEnabled { get; init; } = false; public HashSet ProtectDomainList { get; init; } = new(); - public int ProtectSocksPort { get; init; } = 0; + // -> tun inbound --(if routing proxy)--> relay outbound + // -> proxy core (relay inbound --> proxy outbound --(dialerProxy)--> protect outbound) + // -> protect inbound -> direct proxy outbound data -> internet + public int TunProtectSsPort { get; init; } = 0; + public int ProxyRelaySsPort { get; init; } = 0; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index c0ce7154..1c08fcc5 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -61,6 +61,51 @@ public partial class CoreConfigSingboxService(CoreConfigContext context) ret.Success = true; ret.Data = ApplyFullConfigTemplate(); + if (context.TunProtectSsPort is > 0 and <= 65535) + { + var ssInbound = new + { + type = "shadowsocks", + tag = "tun-protect-ss", + listen = Global.Loopback, + listen_port = context.TunProtectSsPort, + method = "none", + password = "none", + }; + var directRule = new Rule4Sbox() + { + inbound = new List { ssInbound.tag }, + outbound = Global.DirectTag, + }; + var singboxConfigNode = JsonUtils.ParseJson(ret.Data.ToString())!.AsObject(); + var inboundsNode = singboxConfigNode["inbounds"]!.AsArray(); + inboundsNode.Add(JsonUtils.SerializeToNode(ssInbound, new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + })); + var routeNode = singboxConfigNode["route"]?.AsObject(); + var rulesNode = routeNode?["rules"]?.AsArray(); + var protectRuleNode = JsonUtils.SerializeToNode(directRule, + new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); + if (rulesNode != null) + { + rulesNode.Insert(0, protectRuleNode); + } + else + { + var newRulesNode = new JsonArray() { protectRuleNode }; + if (routeNode is null) + { + var newRouteNode = new JsonObject() { ["rules"] = newRulesNode }; + singboxConfigNode["route"] = newRouteNode; + } + else + { + routeNode["rules"] = newRulesNode; + } + } + ret.Data = JsonUtils.Serialize(singboxConfigNode); + } return ret; } catch (Exception ex) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs index ab9a9d9f..3a27c7da 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs @@ -7,10 +7,11 @@ public partial class CoreConfigSingboxService try { var listen = "0.0.0.0"; + var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); _coreConfig.inbounds = []; - if (!_config.TunModeItem.EnableTun - || (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box)) + if (!context.IsTunEnabled + || (context.IsTunEnabled && _node.Port != listenPort)) { var inbound = new Inbound4Sbox() { @@ -20,7 +21,7 @@ public partial class CoreConfigSingboxService }; _coreConfig.inbounds.Add(inbound); - inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); + inbound.listen_port = listenPort; if (_config.Inbound.First().SecondLocalPortEnabled) { @@ -49,7 +50,7 @@ public partial class CoreConfigSingboxService } } - if (_config.TunModeItem.EnableTun) + if (context.IsTunEnabled) { if (_config.TunModeItem.Mtu <= 0) { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index 3ad71592..8187f854 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -15,6 +15,10 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) var ret = new RetResult(); try { + if (context.IsTunEnabled && context.TunProtectSsPort > 0 && context.ProxyRelaySsPort > 0) + { + return GenerateClientProxyRelayConfig(); + } if (_node == null || !_node.IsValid()) { @@ -262,5 +266,106 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) } } + public RetResult GenerateClientProxyRelayConfig() + { + var ret = new RetResult(); + try + { + if (_node == null + || !_node.IsValid()) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (_node.GetNetwork() is nameof(ETransport.quic)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; + return ret; + } + + var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + GenLog(); + _coreConfig.outbounds.Clear(); + GenOutbounds(); + + var protectNode = new ProfileItem() + { + CoreType = ECoreType.sing_box, + ConfigType = EConfigType.Shadowsocks, + Address = Global.Loopback, + Port = context.TunProtectSsPort, + Password = Global.None, + }; + protectNode.SetProtocolExtra(protectNode.GetProtocolExtra() with + { + SsMethod = Global.None, + }); + + foreach (var outbound in _coreConfig.outbounds.Where(outbound => outbound.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) + { + outbound.streamSettings ??= new StreamSettings4Ray(); + outbound.streamSettings.sockopt ??= new Sockopt4Ray(); + outbound.streamSettings.sockopt.dialerProxy = "tun-project-ss"; + } + _coreConfig.outbounds.Add(new CoreConfigV2rayService(context with + { + Node = protectNode, + }).BuildProxyOutbound("tun-project-ss")); + + var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 }; + _coreConfig.routing.rules = + [ + new() + { + inboundTag = new List { "proxy-relay-ss" }, + outboundTag = hasBalancer ? null : Global.ProxyTag, + balancerTag = hasBalancer ? Global.ProxyTag : null, + type = "field" + } + ]; + _coreConfig.inbounds.Clear(); + + var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!; + configNode["inbounds"]!.AsArray().Add(new + { + listen = Global.Loopback, + port = context.ProxyRelaySsPort, + protocol = "shadowsocks", + settings = new + { + network = "tcp,udp", + method = Global.None, + password = Global.None, + }, + tag = "proxy-relay-ss", + }); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configNode); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + #endregion public gen function }