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);
}
}