Skip to content

Conversation

@Meo597
Copy link
Collaborator

@Meo597 Meo597 commented Oct 19, 2025

{
  // 示例配置
  "dns": {
    "enableParallelQuery": true
  }
}

DNS 回退(failover)默认是串行的,即默认仅在选中的 DNS 服务器查询失败或 expectedIPsunexpectedIPs 不匹配后才向下一台服务器发起查询。

串行意味着服务器正常时,每次 failover 都需要 1 rtt;而服务器故障时,每次 failover 都需要等 timeoutMs 默认 4 秒。

启用并行查询后,会预先向所有被选中的 DNS 服务器异步发起查询,并执行“动态分组,组内竞速,组间回退”的策略。

并行意味着无论你的 DNS 规则有多复杂,可能会 failover 无数次,都仅需 1 rtt,规则链中也不再有单点故障,导致硬等 timeoutMs 4 秒。

动态分组,被选中的服务器列表中相邻的服务器如果 clientIP skipfailover queryStrategy tag domains expectedIPs unexpectedIPs 完全一样,被视为同一个组。

组内竞速,同组内只要任意一 DNS 服务器查询成功且 expectedIPsunexpectedIPs 匹配到了 IP,则本组视为成功,同时忽略本组其它服务器的结果 (它们的查询仍会继续,以预热缓存,如果 disableCache: false)。

组间回退,若第一个组还在查询,则等待。若第一个组成功,则返回 IP。若第一个组所有服务器都查询失败或 IP 不匹配,则回退到下一个组。最终若所有组都失败,则返回空解析。

感谢 @t-e-s-tweb 的测试与反馈!

@Meo597 Meo597 force-pushed the feat-dns-parallel-query branch from 67cef37 to 8b8abe9 Compare October 19, 2025 02:03
@t-e-s-tweb
Copy link

Is there any with combination of 5237 and 5239?

@Meo597
Copy link
Collaborator Author

Meo597 commented Oct 19, 2025

Is there any with combination of 5237 and 5239?

https://github.com/Meo597/Xray-core/actions/runs/18633074311

@Meo597

This comment was marked as resolved.

@Meo597 Meo597 changed the title feat(dns): Implement parallel query feat(dns): add parallel query Oct 21, 2025
@Meo597 Meo597 requested a review from Fangliding October 27, 2025 23:46
@Meo597
Copy link
Collaborator Author

Meo597 commented Oct 28, 2025

第一版糊的太粗糙了

因为一旦发现需要过滤 IP 就退出竞速模式(虽然并发请求,但是等所有结果返回再匹配)
这样路径上容易有单点故障,多写几组不同服务器也没用,因为没有分组的概念

而且除 IP 过滤外,tag queryStrategy skipFallback clientIP domains 也至关重要

现在改了第二版:
依然是先并发请求所有服务器
但组内竞速,更快的同时,还防止单点故障

为了不增加用户心智负担,特意把并发查询做成傻瓜模式
会自动把附近的规则相似的服务器归为一组
也就是说小白完全不需要理解它是怎么工作的,只要开启就能得到不小的提升


现在的设计能完全 cover 需求,并解决了这些坑:

  • 以前未知域名和被错误分类的域名要多等 1 rtt 四舍五入就是一个亿
  • 未知域名多写服务器就得多 rtt,少写又有单点故障
  • 递归挂的时候(cf今年三次)必须等四秒超时了才能响应
  • 得精心挑递归服务器,防止太慢拖垮速度,说的就是 Cisco + Oracle
{
  "dns": {
    "servers": [
      // 走直连试探 geosite:cn
      // 要求必须返回中国IP
      {
        "tag": "dns-direct",
        "address": "223.5.5.5",
        "skipFallback": true,
        "domains": ["geosite:cn"],
        "expectIPs": ["geoip:cn"]
      },
      {
        "tag": "dns-direct",
        "address": "114.114.114.114",
        "skipFallback": true,
        "domains": ["geosite:cn"],
        "expectIPs": ["geoip:cn"]
      },
      // 走代理试探 geosite:geolocation-!cn
      // 要求必须返回国外IP
      {
        "address": "8.8.8.8",
        "skipFallback": true,
        "domains": ["geosite:geolocation-!cn"],
        "expectIPs": ["geoip:!cn"]
      },
      {
        "address": "1.1.1.1",
        "skipFallback": true,
        "domains": ["geosite:geolocation-!cn"],
        "expectIPs": ["geoip:!cn"]
      },
      {
        // 未知域名、或者是上面规则回落下来的
        // 走代理,但用ECS试探是不是在中国
        // 不是就继续回落,获取代理CDN友好的IP
        "address": "8.8.8.8",
        "clientIp": "222.85.85.85", // 你当地ISP的地址,这样直连CDN友好
        "skipFallback": false,
        "expectIPs": ["geoip:cn"]
      },
      {
        "address": "8.8.4.4",
        "clientIp": "222.85.85.85",
        "skipFallback": false,
        "expectIPs": ["geoip:cn"]
      },
      "1.1.1.1"
    ],
    "tag": "dns-proxy",
    "enableParallelQuery": true
  }
}

本来写那篇用例是想抛砖引玉等有人把并行 DNS 给糊了,没想到还是得自己动手

@Meo597
Copy link
Collaborator Author

Meo597 commented Oct 29, 2025

目前三个 PR 在我的用例下测了几天,没发现什么问题。

只是设计上仍有些小瑕疵:

  • 组内竞速败者组预热缓存害怕上层取消 ctx (已解决)
    没看那块代码。使用没发现问题,设计上感觉脆弱
    直觉上,上层应该是超时取消而不是返回立即取消,否则乐观缓存也不可能异步刷新成功
  • 乐观缓存工作在 nameserver 级,竞速时其它 ns 可能 ttl 未过期,现在可能返回乐观缓存结果非最优
    并发模式下应该在 lookupip 再套一层缓存还能省点 cpu,而且返回的 ttl 也不会像现在这样乱跳
    这样设计如果用户进一步把原来的 ns 缓存禁用还能省点内存,但回源次数可能变多特别是上游 ttl 不一致时
  • 有些罕见的回落不应该预先并发请求,而是等真的回落了再做,比如走代理但 geosite:geolocation-!cn 响应了 cn ip
    类似 finalQuery 加个 parallelLazy 开关
  • 因 anycast, ecs 等原因一些递归解析结果非最优,写上去只是想当备份。现在只能靠禁用这台的缓存实现
    或许可以加个延迟发起请求,这样也能 cover 上一条需求

仅备忘,暂时不想动了
现在已经很够用了

@t-e-s-tweb
Copy link

t-e-s-tweb commented Nov 5, 2025

Getting a lot of these context cancelled errors. Also not getting any cache OPTIMISE in logs or the previous cache HIT in debug logs. I am using these in the order that you mentioned with rebases.
Screenshot_2025-11-05-02-27-31-52_9bb1ebbbe77763a71e2076eef80e3e18

@Meo597

Edit: added additional logs with dnslog. The site hkping.igamecj.com has very small TTL. So better for testing.

Screenshot_2025-11-05-19-55-18-49

@Meo597
Copy link
Collaborator Author

Meo597 commented Nov 5, 2025

@t-e-s-tweb
乐观缓存和并发查询等相互冲突,不能直接简单地 rebase
你使用的是这个版本吗?它在我这几个设备持续工作一周了
https://github.com/Meo597/Xray-core/tree/rebased-new-dns/parallel-query

@t-e-s-tweb
Copy link

@t-e-s-tweb 乐观缓存和并发查询等相互冲突,不能直接简单地 rebase 你使用的是这个版本吗?它在我这几个设备持续工作一周了 https://github.com/Meo597/Xray-core/tree/rebased-new-dns/parallel-query

Yes. This is the one that I am using. Its working fine. Only the logs show context cancelled or empty responses but the sites are browsable and connecting. But there isn't anyway to check if cache is being hit or optimistic is being used. Parallel queries logs do show them working.

@Meo597
Copy link
Collaborator Author

Meo597 commented Nov 5, 2025

因为我把 debug 日志那几行给注释掉了,它们设计的不好,太“重”了
缓存命中状态现在应该是只在 dns log 而非 debug 中显示

我晚点看一下 ctx 取消是咋回事

@t-e-s-tweb
Copy link

因为我把 debug 日志那几行给注释掉了,它们设计的不好,太“重”了 缓存命中状态现在应该是只在 dns log 而非 debug 中显示

我晚点看一下 ctx 取消是咋回事

Thanks. Here is the complete logcat for the domain.

Logcat.txt

@Meo597
Copy link
Collaborator Author

Meo597 commented Nov 5, 2025

Logcat.txt

  • 组内竞速败者组预热缓存害怕上层取消 ctx
    没看那块代码。使用没发现问题,设计上感觉脆弱
    直觉上,上层应该是超时取消而不是返回立即取消,否则乐观缓存也不可能异步刷新成功

之前就猜测过这块可能有问题,但我只用 udp 所以没发现
他不会导致任何出错,但影响缓存预热效果,我需要优化这一块

@Meo597
Copy link
Collaborator Author

Meo597 commented Nov 5, 2025

然后被我注释掉的那两行 cache debug log
想了想还是应该加回去

因为我搜了下各种热点函数都在 errors.LogDebug
也不差这两行了

Logger 应该先判定 level 再拼字符串

@Meo597
Copy link
Collaborator Author

Meo597 commented Nov 7, 2025

@t-e-s-tweb 感谢测试与反馈!

Getting a lot of these context cancelled errors.

现在,非 disableCache 的服务器,处于并发查询模式下,会忽略 caller 的 ctx.cancel() 以充分预热缓存。
我只用 udp 没发现此问题,是因为 udp 不受 caller 的 ctx 控制。


Also not getting any cache OPTIMISE in logs or the previous cache HIT in debug logs.

把 debug 日志加回去了。


测试时还发现一个 xray 原有的 bug:
其中一台服务器没有任何响应(比如网络错误),另一台服务器工作正常但不响应 ip 时(比如被规则过滤掉、比如不存在的记录)
预期应该是给客户端响应正常工作的那台服务器的结果:rcode 或空 ip
实际是不响应,导致 nslookup 和 dig 都会超时重试几次
顺手在这个 pr 改成能响应

其实这样还是有问题,如果什么 app 无视 rcode 3 疯狂重试
在一台服务器挂了的情况下缓存 rcode 3 没有任何意义
要么遇到 rcode 3 直接返回,不再尝试其它服务器
要么把上游服务器网络错误也给缓存一小会儿
要么把故障的服务器标记为故障一会儿也行
或者是实现上面说的给 lookupip 套一层缓存,而不是现在的 per nameserver

@RPRX
Copy link
Member

RPRX commented Nov 21, 2025

@Meo597 rebase

@Meo597 Meo597 force-pushed the feat-dns-parallel-query branch from 639a2f1 to 77c064c Compare November 21, 2025 05:43
@Meo597
Copy link
Collaborator Author

Meo597 commented Nov 21, 2025

ok

@RPRX RPRX merged commit f14fd1c into XTLS:main Nov 21, 2025
39 checks passed
@Meo597 Meo597 deleted the feat-dns-parallel-query branch November 21, 2025 07:22
@Meo597 Meo597 removed the request for review from Fangliding December 1, 2025 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants