diff --git a/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props index 83861b8e..75a47cea 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -9,7 +9,7 @@ - + diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayIPNetworkTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayIPNetworkTests.cs new file mode 100644 index 00000000..36641aad --- /dev/null +++ b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayIPNetworkTests.cs @@ -0,0 +1,128 @@ +namespace ServiceLib.Tests.CoreConfig.V2ray; + +public class CoreConfigV2rayIPNetworkTests +{ + [Test] + [Arguments("0.0.0.0/0", "::/0")] + [Arguments("::/0", "0.0.0.0/0")] + [Arguments("0.0.0.0/1", "128.0.0.0/1,::/0")] + [Arguments("::/1", "0.0.0.0/0,8000::/1")] + public async Task GenerateClientConfigContent_TunExclusion_ShouldPreserveRemainingRoutes( + string excludedNetwork, string expectedRoutes) + { + var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, true); + config.TunModeItem.RouteExcludeAddress = [excludedNetwork]; + var generated = await GenerateConfig(config); + + var routes = generated.inbounds.Single(i => i.protocol == "tun").settings.autoSystemRoutingTable; + await routes.Should().BeEquivalentTo(expectedRoutes.Split(','), StringComparer.Ordinal); + } + + [Test] + public async Task GenerateClientConfigContent_TunExclusions_ShouldHandleOverlappingAndDuplicateNetworks() + { + var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, true); + config.TunModeItem.RouteExcludeAddress = + [ + "0.0.0.0/1", "128.0.0.1/2", "128.0.0.1/2", "128.0.0.1/32", + "::/1", "8000::1/2", "8000::1/128" + ]; + var generated = await GenerateConfig(config); + + var routes = generated.inbounds.Single(i => i.protocol == "tun").settings.autoSystemRoutingTable; + await routes.Should().BeEquivalentTo(new[] { "192.0.0.0/2", "c000::/2" }, StringComparer.Ordinal); + } + + [Test] + public async Task GenerateClientConfigContent_TunExclusions_ShouldAllowExcludingBothAddressFamilies() + { + var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, true); + config.TunModeItem.RouteExcludeAddress = ["0.0.0.0/0", "::/0"]; + var generated = await GenerateConfig(config); + + var routes = generated.inbounds.Single(i => i.protocol == "tun").settings.autoSystemRoutingTable; + await routes.Should().BeEmpty(); + } + + [Test] + public async Task GenerateClientConfigContent_TunHostExclusions_ShouldPreserveNeighbouringAddresses() + { + var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, true); + config.TunModeItem.RouteExcludeAddress = + [ + "0.0.0.0/32", "255.255.255.255/32", + "::/128", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" + ]; + var generated = await GenerateConfig(config); + var routes = generated.inbounds.Single(i => i.protocol == "tun").settings.autoSystemRoutingTable + .Select(IPNetwork.Parse).ToArray(); + + foreach (var address in new[] + { + "0.0.0.0", "255.255.255.255", "::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" + }) + { + await routes.Any(route => route.Contains(IPAddress.Parse(address))).Should().BeFalse(); + } + foreach (var address in new[] + { + "0.0.0.1", "255.255.255.254", "::1", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe" + }) + { + await routes.Any(route => route.Contains(IPAddress.Parse(address))).Should().BeTrue(); + } + } + + [Test] + [Arguments("198.18.0.0/15", 131071L)] + [Arguments("0.0.0.0/0", 4294967295L)] + [Arguments("198.18.0.0/31", 1L)] + [Arguments("198.18.0.1/32", 65535L)] + [Arguments("fd00::/64", long.MaxValue)] + [Arguments("fd00::/66", 4611686018427387903L)] + [Arguments("fd00::/127", 1L)] + [Arguments("fd00::1/128", 65535L)] + [Arguments("invalid-network", 65535L)] + [Arguments("198.18.0.0/33", 65535L)] + [Arguments("fd00::/129", 65535L)] + public async Task GenerateClientConfigContent_FakeDns_ShouldDerivePoolSizeOrUseFallback( + string range, long expectedPoolSize) + { + var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray); + config.SimpleDNSItem.FakeIP = true; + config.SimpleDNSItem.FakeIPRange = range; + CoreConfigTestFactory.BindAppManagerConfig(config); + var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray); + context.RoutingItem!.RuleSet = JsonUtils.Serialize(new List + { + new() + { + Enabled = true, + RuleType = ERuleType.DNS, + OutboundTag = Global.ProxyTag, + Domain = ["full:example.com"], + } + }); + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + await result.Success.Should().BeTrue(); + var generated = JsonUtils.Deserialize(result.Data!.ToString())!; + + await generated.fakedns.Should().NotBeNull(); + await generated.fakedns!.ipPool.Should().BeEqualTo(range); + await generated.fakedns.poolSize.Should().BeEqualTo(expectedPoolSize); + } + + private static async Task GenerateConfig(Config config) + { + CoreConfigTestFactory.BindAppManagerConfig(config); + var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray); + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var generated = JsonUtils.Deserialize(result.Data!.ToString()); + await generated.Should().NotBeNull(); + return generated!; + } +}