From b6cfce069abe0fda66df2fce64d2b6c8f8e97bfc Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 5 Jun 2026 01:54:57 +0000 Subject: [PATCH] Xray Route Exclude Address (#9469) * Add IPNetwork2 package * Xray Route Exclude Address --------- Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com> --- v2rayN/Directory.Packages.props | 1 + .../CoreConfig/CoreConfigTestFactory.cs | 10 ++- .../V2ray/CoreConfigV2rayServiceTests.cs | 23 +++++++ .../Builder/CoreConfigContextBuilder.cs | 14 ++++ v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 9 +++ v2rayN/ServiceLib/Resx/ResUI.resx | 3 + v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 3 + v2rayN/ServiceLib/ServiceLib.csproj | 1 + .../CoreConfig/V2ray/V2rayInboundService.cs | 69 +++++++++++++++++-- 9 files changed, 125 insertions(+), 8 deletions(-) diff --git a/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props index 766f4c42..086c726f 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -11,6 +11,7 @@ + diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs b/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs index 812a5a32..63bc45ca 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs @@ -187,7 +187,7 @@ internal static class CoreConfigTestFactory SimpleDnsItem = config.SimpleDNSItem, AllProxiesMap = new Dictionary { [node.IndexId] = node }, FullConfigTemplate = null, - IsTunEnabled = false, + IsTunEnabled = config.TunModeItem.EnableTun, ProtectDomainList = [], }; } @@ -206,4 +206,12 @@ internal static class CoreConfigTestFactory config.SimpleDNSItem.BootstrapDNS = bootstrapDns; return config; } + + public static Config CreateConfigWithTunRouteExcludeAddress(ECoreType coreType) + { + var config = CreateConfig(coreType); + config.TunModeItem.EnableTun = true; + config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"]; + return config; + } } diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs index 29b26648..4847050d 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs @@ -536,4 +536,27 @@ public class CoreConfigV2rayServiceTests directOutbound.Should().NotBeNull(); directOutbound!.settings.domainStrategy.Should().Be("UseIPv4"); } + + [Fact] + public void GenerateClientConfigContent_TunRouteExcludeAddress() + { + var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray); + CoreConfigTestFactory.BindAppManagerConfig(config); + + var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main"); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray); + + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + result.Success.Should().BeTrue(); + + var cfg = JsonUtils.Deserialize(result.Data!.ToString())!; + var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun"); + + tunInbound.Should().NotBeNull(); + + tunInbound!.settings.autoSystemRoutingTable.Should().NotContain("0.0.0.0/0"); + tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32"); + tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31"); + } } diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index eb1af21b..b7481e1a 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -91,6 +91,20 @@ public class CoreConfigContextBuilder context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode; } } + if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 }) + { + foreach (var addr in context.AppConfig.TunModeItem.RouteExcludeAddress) + { + try + { + IPNetwork2.Parse(addr); + } + catch + { + validatorResult.Errors.Add(string.Format(ResUI.MsgTunRouteExcludeInvalidAddress, addr)); + } + } + } return new CoreConfigContextBuilderResult(context, validatorResult); } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 14c5daee..b7cf06da 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -2229,6 +2229,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Invalid address in TUN route exclude list: {0} 的本地化字符串。 + /// + public static string MsgTunRouteExcludeInvalidAddress { + get { + return ResourceManager.GetString("MsgTunRouteExcludeInvalidAddress", resourceCulture); + } + } + /// /// 查找类似 Unpacking... 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 3a1ad0b8..ec97d10a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1758,4 +1758,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Use commas (,) to separate. + + Invalid address in TUN route exclude list: {0} + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 09a0ace5..4654b723 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1755,4 +1755,7 @@ 使用逗号 (,) 分隔 + + TUN 路由排除列表包含无效地址:{0} + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ServiceLib.csproj b/v2rayN/ServiceLib/ServiceLib.csproj index 6a506e36..5b0b8072 100644 --- a/v2rayN/ServiceLib/ServiceLib.csproj +++ b/v2rayN/ServiceLib/ServiceLib.csproj @@ -6,6 +6,7 @@ + true diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs index 239c68ad..7f950a83 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs @@ -30,14 +30,19 @@ public partial class CoreConfigV2rayService inbound3.listen = listen; _coreConfig.inbounds.Add(inbound3); - //auth + // auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) { inbound3.settings.auth = "password"; - inbound3.settings.accounts = new List - { - new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } - }; + inbound3.settings.accounts = + [ + new() + { + user = _config.Inbound.First().User, + pass = _config.Inbound.First().Pass, + }, + + ]; } } else @@ -53,12 +58,15 @@ public partial class CoreConfigV2rayService { _config.TunModeItem.Mtu = Global.TunMtus.First(); } - var tunInbound = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ?? new Inbounds4Ray { }; + var tunInbound = + JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ?? + new Inbounds4Ray(); tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun"; tunInbound.settings.MTU = _config.TunModeItem.Mtu; - if (_config.TunModeItem.EnableIPv6Address == false) + if (!_config.TunModeItem.EnableIPv6Address) { tunInbound.settings.gateway = ["172.18.0.1/30"]; + tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"]; } var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx(); if (!bindInterface.IsNullOrEmpty()) @@ -66,6 +74,53 @@ public partial class CoreConfigV2rayService tunInbound.settings.autoOutboundsInterface = bindInterface; } tunInbound.sniffing = inbound.sniffing; + + if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 }) + { + var wholeInternet = IPNetwork2.Parse("0.0.0.0/0"); + var wholeInternetV6 = IPNetwork2.Parse("::/0"); + + var excludeList = _config.TunModeItem.RouteExcludeAddress.Select(IPNetwork2.Parse) + .Where(x => x != null).ToList(); + + var includeList = new List { wholeInternet }; + var includeListV6 = new List { wholeInternetV6 }; + + foreach (var exclude in excludeList) + { + var temp = new List(); + if (exclude.AddressFamily == AddressFamily.InterNetwork) + { + foreach (var net in includeList) + { + temp.AddRange(net.Subtract(exclude)); + } + includeList = temp; + } + else if (exclude.AddressFamily == AddressFamily.InterNetworkV6) + { + foreach (var net in includeListV6) + { + temp.AddRange(net.Subtract(exclude)); + } + includeListV6 = temp; + } + } + + includeList = IPNetwork2.Supernet(includeList.ToArray()).ToList(); + includeListV6 = IPNetwork2.Supernet(includeListV6.ToArray()).ToList(); + + if (_config.TunModeItem.EnableIPv6Address) + { + tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()) + .Concat(includeListV6.Select(x => x.ToString())).ToList(); + } + else + { + tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()).ToList(); + } + } + _coreConfig.inbounds.Add(tunInbound); } }