Xray Route Exclude Address (#9469)

* Add IPNetwork2 package

* Xray Route Exclude Address

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
This commit is contained in:
DHR60
2026-06-05 01:54:57 +00:00
committed by GitHub
parent 39ec9239d2
commit b6cfce069a
9 changed files with 125 additions and 8 deletions

View File

@@ -11,6 +11,7 @@
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.17" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
<PackageVersion Include="CliWrap" Version="3.10.1" />
<PackageVersion Include="Downloader" Version="5.7.0" />

View File

@@ -187,7 +187,7 @@ internal static class CoreConfigTestFactory
SimpleDnsItem = config.SimpleDNSItem,
AllProxiesMap = new Dictionary<string, ProfileItem> { [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;
}
}

View File

@@ -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<V2rayConfig>(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");
}
}

View File

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

View File

@@ -2229,6 +2229,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Invalid address in TUN route exclude list: {0} 的本地化字符串。
/// </summary>
public static string MsgTunRouteExcludeInvalidAddress {
get {
return ResourceManager.GetString("MsgTunRouteExcludeInvalidAddress", resourceCulture);
}
}
/// <summary>
/// 查找类似 Unpacking... 的本地化字符串。
/// </summary>

View File

@@ -1758,4 +1758,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
<value>Use commas (,) to separate.</value>
</data>
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
<value>Invalid address in TUN route exclude list: {0}</value>
</data>
</root>

View File

@@ -1755,4 +1755,7 @@
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
<value>使用逗号 (,) 分隔</value>
</data>
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
<value>TUN 路由排除列表包含无效地址:{0}</value>
</data>
</root>

View File

@@ -6,6 +6,7 @@
<ItemGroup>
<PackageReference Include="Downloader" />
<PackageReference Include="IPNetwork2" />
<PackageReference Include="ReactiveUI">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>

View File

@@ -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<AccountsItem4Ray>
{
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<Inbounds4Ray>(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ?? new Inbounds4Ray { };
var tunInbound =
JsonUtils.Deserialize<Inbounds4Ray>(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<IPNetwork2> { wholeInternet };
var includeListV6 = new List<IPNetwork2> { wholeInternetV6 };
foreach (var exclude in excludeList)
{
var temp = new List<IPNetwork2>();
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);
}
}