Skip to content

feat(xdns): multi-resolver fan-out + fix(kcp): reduce aggressive retransmissions#5872

Open
nnemirovsky wants to merge 25 commits intoXTLS:mainfrom
nnemirovsky:xdns-resolver-multiplexing
Open

feat(xdns): multi-resolver fan-out + fix(kcp): reduce aggressive retransmissions#5872
nnemirovsky wants to merge 25 commits intoXTLS:mainfrom
nnemirovsky:xdns-resolver-multiplexing

Conversation

@nnemirovsky
Copy link
Copy Markdown

Cleaned up resubmission of #5871. Removed unnecessary files, squashed to 2 commits.

Changes

1. XDNS multi-resolver fan-out

Optional resolvers config field. When set, the client distributes DNS queries across multiple public resolvers within a single mKCP session for higher throughput.

"finalmask": {
  "udp": [{"type": "xdns", "settings": {"domain": "t.example.com", "resolvers": ["1.1.1.1", "8.8.8.8"]}}]
}
  • One UDP socket per resolver with independent receive goroutines
  • Round-robin distribution
  • Backward compatible (omitting resolvers = direct mode)
  • Fix server sendLoop: drain stale queries, reduce response delay from 1s to 50ms, increase write queue from 512 to 4096

2. mKCP: respect congestion control (per RPRX suggestion in #5871)

The cwnd *= 20 multiplier allowed 20x more in-flight packets than the congestion window, defeating congestion control. When congestion: true, the multiplier is now skipped so mKCP doesn't flood low-bandwidth transports like XDNS.

Connection timeout increased from 30s to 120s to accommodate DNS tunnel latency.

Test plan

  • TestParseResolverAddr: resolver address parsing
  • TestResolverModeRoundTrip: mock resolver end-to-end
  • TestMultiResolverDistribution: round-robin verification
  • TestDirectModeRoundTrip / TestResolverModeServerToClient: bidirectional data
  • HTTPS verified working through XDNS on localhost

Add optional `resolvers` config field to XDNS finalmask. When set,
the client sends DNS queries through public DNS resolvers instead of
connecting directly to the server on port 53.

- One UDP socket per resolver with independent receive goroutines
- Round-robin query distribution across resolvers
- Backward compatible: omitting resolvers preserves direct mode
- Fix server sendLoop starvation under mKCP retransmission flood
- Drain excess query records to skip stale queries
- Reduce server response delay from 1s to 50ms
- Increase server write queue from 512 to 4096
…s enabled

The cwnd *= 20 multiplier allowed 20x more packets in flight than the
congestion window, defeating the purpose of congestion control. When
congestion is enabled, respect the actual cwnd without the multiplier.
This prevents mKCP from flooding low-bandwidth transports like XDNS.

Also increase connection timeout from 30s to 120s to accommodate
high-latency transports like DNS tunneling.
@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Mar 31, 2026

终于知道为什么反感 AI 了

粗略看了下,我设计 xdns 里的 closed 不需要 sync,不存在竞态,xdns 的服务端天然支持从不同 dns 收发数据,理论无需改动,多 conn 收发也只需改动客户端

怎么说呢,我能理解你的想法,但放在 mask 里并不合适,mask 不应该进行额外的 dial,目前是允许 xdns 在任意层的,虽然不在最后一层没有什么用

如果要接受这个 pr,要么把 xdns 剥离到独立传输层,要么加上限制 xdns 和 xicmp 一样只能处于最外层,且不能搭配 udphop 与 dialerproxy

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

或许可以利用 writeto 的 addr 特性来实现 multi dns,如果能不新建 conn,那么可以留在 mask

Address LjhAUMEM review: masks should not create new connections.
Use WriteTo addr on the existing PacketConn to send to different
resolvers instead of creating separate sockets. Revert server
changes (server already supports data from different DNS sources).
Remove sockopt files, sync changes, and layer validation.
@RPRX
Copy link
Copy Markdown
Member

RPRX commented Mar 31, 2026

或许可以利用 writeto 的 addr 特性来实现 multi dns,如果能不新建 conn,那么可以留在 mask

结果被运营商按源端口限速

XDNS 可以限制为必须在最外层,也就 noise 有点用,udphop 对它来说没啥用,dialerproxy 实在不行可以利用 tun 来实现

话说我一直想把出站的 address port 完全移到传输层来着,可能移了的话对这种情况来说更方便

没看代码,这个特性就简单些用数组而不是 map 吧,随机选择,重复次数多的 ip 更容易被选中

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Mar 31, 2026

@LjhAUMEM 或许改一下 noise 让它兼容一下 XDNS 在倒数第二层的情况

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Mar 31, 2026

但公共 UDP 明文 DNS 基本上都是 53 端口而且这时候 DNS 又不需要握手,可能审查者就直接按 DNS 按单个包分析流量了

所以 noise 对 XDNS 有没有用我也不清楚

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

@LjhAUMEM 或许改一下 noise 让它兼容一下 XDNS 在倒数第二层的情况

可以搞成忽略前面所有的 noise

话说我想等串流那个解决了再来审这个

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

不过这个最好搭配个 mux,kcp 客户端没有 mux 单条连接其实不会持续很久,客户端日志的大量 xdns closed 能证明这一点,再加上 multi dns,监听的端口几倍增长

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Mar 31, 2026

话说我想等串流那个解决了再来审这个

那个我感觉可能是 DispatchLink() 的问题

不过这个最好搭配个 mux

XDRIVE 强制 mux 所以 mux 的增强和分享已经提上日程了

@hippo2025
Copy link
Copy Markdown

If some of the DNS resolvers in the list become unavailable will it quickly switch to using the others?

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

@nnemirovsky You can keep the original multi-connection method, but it should be possible to modify only the client side without changing the server side.

Per review: separate UDP sockets per resolver avoids ISP source-port
rate limiting. Each resolver has its own recvLoop goroutine. Client-only
changes, server unchanged.
@nnemirovsky
Copy link
Copy Markdown
Author

@hippo2025 Currently it's round-robin without failover. If a resolver stops responding, queries sent to it are lost and KCP handles retransmission on the next round. With 3 resolvers and 1 dead, throughput drops ~33% but the tunnel stays up.

@LjhAUMEM Updated to use separate sockets per resolver (no server changes). Reverted all sync/server modifications from earlier.

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

@nnemirovsky Do you mind if I make changes directly on your branch?

@RPRX 话说我是不是没权限直接给他的分支提交

@Fangliding
Copy link
Copy Markdown
Member

单个文件的修改可以点铅笔 要大改我一般都是关了在本地重新弄一个 写co author就是了

@nnemirovsky
Copy link
Copy Markdown
Author

@LjhAUMEM go ahead, feel free to push directly to the branch.

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

单个文件的修改可以点铅笔 要大改我一般都是关了在本地重新弄一个 写co author就是了

我认为这样做可能不太礼貌,改的确实有点多,开新 pr 的话那晚点再说了

@LjhAUMEM go ahead, feel free to push directly to the branch.

I just tried and I don't have permission to commit. It seems I can only submit pull requests to your branch, but I already have a core fork, so it looks like I'll have to put this on hold for now.

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Mar 31, 2026

@LjhAUMEM 给你 Maintain 权限了

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 1, 2026

@nnemirovsky I think we can start testing

{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 1080,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "address": "127.0.0.1",
        "port": 53,
        "id": "5783a3e7-e373-51cd-8642-c83782b807c5",
        "encryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "kcpSettings": {
          "mtu": 130
        },
        "finalmask": {
          "udp": [
            {
              "type": "xdns",
              "settings": {
                "domain": "", // 1
                "resolvers": [
                  "8.8.8.8:53", 
                  "1.1.1.1:53", 
                  "[2001:4860:4860::8888]:53",
                  "[2606:4700:4700::1111]:53"
                ]
              }
            }
          ]
        }
      }
    }
  ]
}
{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      // "listen": "127.0.0.1",
      "port": 53,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "5783a3e7-e373-51cd-8642-c83782b807c5"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "kcpSettings": {
          "mtu": 900
        },
        "finalmask": {
          "udp": [
            {
              "type": "xdns",
              "settings": {
                "domain": "" // 1
              }
            }
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    }
  ]
}

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 6, 2026

不是,我说这个域名的意思是,服务端也支持多个域名,然后客户端可以任意选用,不是用域名解析出 IP 地址

就和 ECH 那个拿别的域名查 config 是一个道理

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 6, 2026

done,不过可能得说明下 domains 仅服务端,代码里没对客户端做检查

@hippo2025
Copy link
Copy Markdown

The client must send queries to the public DNS resolvers with the correct domain. If there are multiple domains, then the client must send different queries.

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 6, 2026

The client must send queries to the public DNS resolvers with the correct domain. If there are multiple domains, then the client must send different queries.

自己开多 outbound + balancers,还没迎来重构呢还一个劲的堆功能

@hippo2025
Copy link
Copy Markdown

Well, sorry, maybe it's OK for now. If the server supports multiple domains, then the cliend could have different outbounds for different domains, there is no real need to use several domains simultaneously.

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 6, 2026

直接 break 掉现有配置吧,只支持 domains 和 resolvers,后者要写 example.com+udp://1.1.1.1:53 这样指定域名和 UDP

也就是说 proxy 那里填的 address 和 port 都扔了,比 XICMP 还狠

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 6, 2026

但是要那后面的 1.1.1.1 有啥用,服务端只能 udp 53

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 6, 2026

1.1.1.1 是 public DNS 啊,这个 PR 不就是为了 multi-resolver 吗

domains 是服务端的,resolvers 是客户端的 example.com+udp://1.1.1.1:53 这样的格式,先只支持 UDP

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 6, 2026

卧槽,终于看懂了,我智商好像下降了

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 6, 2026

我也很奇怪为啥说了一天没说通

@LjhAUMEM LjhAUMEM force-pushed the xdns-resolver-multiplexing branch from 48eda89 to bb3304e Compare April 7, 2026 02:38
@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 7, 2026

应该可以了

{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 1080,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "address": "127.0.0.1",
        "port": 53,
        "id": "5783a3e7-e373-51cd-8642-c83782b807c5",
        "encryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "kcpSettings": {
          "mtu": 130
        },
        "finalmask": {
          "udp": [
            {
              "type": "xdns",
              "settings": {
                "resolvers": [
                  "xxx+udp://8.8.8.8:53", 
                  "xxx+udp://1.1.1.1:53", 
                  "xxx+udp://[2001:4860:4860::8888]:53",
                  "xxx+udp://[2606:4700:4700::1111]:53"
                ]
              }
            }
          ]
        }
      }
    }
  ]
}
{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      // "listen": "127.0.0.1",
      "port": 53,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "5783a3e7-e373-51cd-8642-c83782b807c5"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "kcpSettings": {
          "mtu": 900
        },
        "finalmask": {
          "udp": [
            {
              "type": "xdns",
              "settings": {
                "domains": [
                  "xxx"
                ]
              }
            }
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    }
  ]
}

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 7, 2026

@LjhAUMEM 是否有把旧版配置 break 掉,resolvers 是否支持多个相同的项(代表选中几率更高),是否是 crypto/rand

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 7, 2026

旧配置 break 了

resolvers 是否支持多个相同的项(代表选中几率更高)

支持,不过域名和 ip 不是绑定的,可能出现 resolvers1 的 domain + resolvers4 的 ip

是否是 crypto/rand

resolverIdx 只在 sendLoop 被修改,是轮询,但 writeto 里的 encode with domain 和这个不是同步

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 7, 2026

旧配置 break 了

报错一下提醒迁移至新配置,能判断下出入站吗,防止位置写错了

轮询

轮询改成随机比较好吧


另外 ICMP 有一个这问题 #5879 不知道是不是 ping 被限速了,另开个 PR 也改成多 ip 的形式吧,再支持下掩码,利好 IPv6

这下 XDNS 和 XICMP 都不看 proxy 里填的 address 和 port 了

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 7, 2026

能判断下出入站吗

好像不行

轮询改成随机比较好吧

那就得先轮一圈取出活的 dns,再进行随机,现在的轮询碰到第一个活的就可以发了

另外 ICMP 有一个这问题 #5879 不知道是不是 ping 被限速了,另开个 PR 也改成多 ip 的形式吧,再支持下掩码,利好 IPv6

这下 XDNS 和 XICMP 都不看 proxy 里填的 address 和 port 了

那是因为要有客户端 echo 服务端才发 reply,多 ip 我晚点看看

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 7, 2026

啊还要判断 DNS 是不是活的吗,我觉得直接发就行了,用户既然敢写就默认它是可用的,没回复的话一次性 warn 一下?

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 7, 2026

那现在针对没写 domains/resolvers 的报错有吗,针对 resolvers 中不是 udp:// 的报错有吗

@hippo2025
Copy link
Copy Markdown

啊还要判断 DNS 是不是活的吗,我觉得直接发就行了,用户既然敢写就默认它是可用的,没回复的话一次性 warn 一下?

Many public DNS resolvers are rate limited, also their rate limits could change quickly. It'll be hard to update share links quickly. Also, you might have 2 users connected to different ISPs, and some public DNS will work on one ISP and not on another. So if XDNS can drop dead resolvers or resolvers with ping that is too high, it would be great

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 7, 2026

啊还要判断 DNS 是不是活的吗,我觉得直接发就行了,用户既然敢写就默认它是可用的,没回复的话一次性 warn 一下?

单个解析器不管活不活都发,多个自带个检测,这个检测几乎不占额外资源,我觉得挺好

那现在针对没写 domains/resolvers 的报错有吗,针对 resolvers 中不是 udp:// 的报错有吗

在dial/listen的时候才能看到,我给infra也加一下吧,done

@RPRX
Copy link
Copy Markdown
Member

RPRX commented Apr 7, 2026

那就轮询吧,不过针对不通的 DNS 要给个一次性的 warn,不然写那么多,有的根本不通都不知道

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 7, 2026

没有输出是因为不通的数据只在一次dial周期,新dial又是新的,而且这个不通不是说一直不用,如果收到回包或者当前在用的变为不可用都将重新启用这个不通的

也就是处于薛定谔的状态,不太好输出,输出了也不一定保证不会复活

@LjhAUMEM
Copy link
Copy Markdown
Collaborator

LjhAUMEM commented Apr 8, 2026

xicmp 要速度还得推翻现有的 ping 架构,等下 #5887,新版先不带 xicmp 吧,不想写 5887 到一半再打断写其他的了

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.

5 participants