From 9f599c60a7b358bc2dc598957421506bc621ae96 Mon Sep 17 00:00:00 2001 From: Desaichk <127425055+Desai0@users.noreply.github.com> Date: Sun, 15 Nov 2026 11:15:10 +0300 Subject: [PATCH] fix(xray): keep direct DNS ahead of catch-all proxy rules (#10599) * fix(xray): keep direct DNS ahead of catch-all proxy rules * fix(xray): cover full nonzero port range in DNS routing --- .../V2ray/CoreConfigV2rayServiceTests.cs | 92 +++++++++++++++++++ .../CoreConfig/V2ray/V2rayDnsService.cs | 16 +++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs index 08b182ce..dc3adf96 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs @@ -2,6 +2,98 @@ namespace ServiceLib.Tests.CoreConfig.V2ray; public class CoreConfigV2rayServiceTests { + [Test] + [Arguments(false, "0-65535", null, Global.ProxyTag)] + [Arguments(false, "1-65535", null, Global.ProxyTag)] + [Arguments(true, "0-65535", null, Global.ProxyTag)] + [Arguments(true, null, "tcp,udp", Global.ProxyTag)] + [Arguments(false, null, null, Global.ProxyTag)] + [Arguments(false, "0-65535", null, Global.BlockTag)] + public async Task DirectDnsRule_ShouldPrecedeCatchAllAndPreserveExplicitRules(bool tun, string? port, string? network, string outbound) + { + var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray); + config.TunModeItem.EnableTun = tun; + config.SimpleDNSItem.DirectDNS = "8.8.8.8"; + config.SimpleDNSItem.RemoteDNS = "1.1.1.1"; + CoreConfigTestFactory.BindAppManagerConfig(config); + var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray); + var userRules = new List + { + new() { RuleType = ERuleType.DNS, Domain = ["domain:direct.example"], OutboundTag = Global.DirectTag }, + new() { InboundTag = ["direct-dns-1"], Ip = ["8.8.8.8"], Port = "53", OutboundTag = Global.BlockTag }, + new() { Port = "443", Network = "tcp", OutboundTag = Global.ProxyTag }, + }; + if (port != null || network != null) + { + userRules.Add(new() { Port = port, Network = network, OutboundTag = outbound }); + } + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray) with + { + RoutingItem = new RoutingItem { RuleSet = JsonUtils.Serialize(userRules), DomainStrategy = Global.IPOnDemand } + }; + + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var rules = JsonUtils.Deserialize(result.Data!.ToString())!.routing.rules; + var direct = rules.FindIndex(r => r.outboundTag == Global.DirectTag && r.inboundTag?.Contains("direct-dns-1") == true); + var explicitBlock = rules.FindIndex(r => r.outboundTag == Global.BlockTag && r.inboundTag?.Contains("direct-dns-1") == true); + var https = rules.FindIndex(r => r.port == "443" && r.network == "tcp" && r.outboundTag == Global.ProxyTag); + await (explicitBlock >= 0 && https > explicitBlock && direct > https).Should().BeTrue(); + if (port != null || network != null) + { + var catchAll = rules.FindIndex(r => r.port == port && r.network == network && r.inboundTag == null && r.outboundTag == outbound); + await (catchAll >= 0 && (outbound == Global.ProxyTag ? catchAll > direct : catchAll < direct)).Should().BeTrue(); + } + if (tun) + { + var intercept = rules.FindIndex(r => r.inboundTag?.Contains("tun") == true && r.port == "53" && r.outboundTag == Global.DnsOutboundTag); + await (intercept >= 0 && intercept < direct).Should().BeTrue(); + } + await rules.Should().Contain(r => r.inboundTag != null && r.inboundTag.Contains(Global.DnsTag) && r.outboundTag == Global.ProxyTag); + } + + [Test] + [Arguments("0-65535", null)] + [Arguments("1-65535", null)] + [Arguments(null, "udp,tcp")] + public async Task DirectDnsRule_ShouldPrecedeCatchAllBalancer(string? port, string? network) + { + var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray); + config.SimpleDNSItem.DirectDNS = "8.8.8.8,https://8.8.4.4/dns-query"; + config.SimpleDNSItem.RemoteDNS = "1.1.1.1"; + CoreConfigTestFactory.BindAppManagerConfig(config); + var n1 = CoreConfigTestFactory.CreateSocksNode(ECoreType.Xray, "n1", "node-1"); + var n2 = CoreConfigTestFactory.CreateSocksNode(ECoreType.Xray, "n2", "node-2"); + var group = CoreConfigTestFactory.CreatePolicyGroupNode(ECoreType.Xray, "g1", "group", [n1.IndexId, n2.IndexId]); + var context = CoreConfigTestFactory.CreateContext(config, group, ECoreType.Xray) with + { + RoutingItem = new RoutingItem + { + RuleSet = JsonUtils.Serialize(new List + { + new() { RuleType = ERuleType.DNS, Domain = ["domain:direct.example"], OutboundTag = Global.DirectTag }, + new() { Port = port, Network = network, OutboundTag = Global.ProxyTag }, + }), + DomainStrategy = Global.AsIs, + }, + }; + context.AllProxiesMap[n1.IndexId] = n1; + context.AllProxiesMap[n2.IndexId] = n2; + + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var rules = JsonUtils.Deserialize(result.Data!.ToString())!.routing.rules; + var direct = rules.FindIndex(r => r.outboundTag == Global.DirectTag && r.inboundTag?.Contains("direct-dns-1") == true); + var catchAll = rules.FindIndex(r => r.port == port && r.network == network && r.inboundTag == null + && r.balancerTag == Global.ProxyTag + Global.BalancerTagSuffix); + await (direct >= 0 && catchAll > direct).Should().BeTrue(); + await rules[direct].inboundTag.Should().Contain("direct-dns-2"); + await rules.Should().Contain(r => r.inboundTag != null && r.inboundTag.Contains(Global.DnsTag) + && r.balancerTag == Global.ProxyTag + Global.BalancerTagSuffix); + } + [Test] [Arguments("demo-seed", "srtp", 2)] [Arguments(null, "srtp", 2)] diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs index 7a388625..ccccd8aa 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -99,7 +99,9 @@ public partial class CoreConfigV2rayService .ToList(); if (directDnsTags.Count > 0) { - _coreConfig.routing.rules.Add(new() + var rules = _coreConfig.routing.rules; + var catchAllIndex = rules.FindIndex(IsDnsRoutingCatchAll); + rules.Insert(catchAllIndex < 0 ? rules.Count : catchAllIndex, new() { type = "field", inboundTag = directDnsTags, @@ -125,6 +127,18 @@ public partial class CoreConfigV2rayService } } + private static bool IsDnsRoutingCatchAll(RulesItem4Ray rule) + { + return (rule.outboundTag == Global.ProxyTag || rule.balancerTag == Global.ProxyTag + Global.BalancerTagSuffix) + && rule.inboundTag is not { Count: > 0 } + && rule.domain is not { Count: > 0 } + && rule.ip is not { Count: > 0 } + && rule.protocol is not { Count: > 0 } + && rule.process is not { Count: > 0 } + && rule.port is null or "0-65535" or "1-65535" + && rule.network is null or "tcp,udp" or "udp,tcp"; + } + private void GenFakeDns() { var fakeipRange = _config.SimpleDNSItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : _config.SimpleDNSItem.FakeIPRange;