zfl9 / chinadns-ng

chinadns 重构增强版,支持域名分流、ipset/nftset、UDP/TCP/DoT
GNU Affero General Public License v3.0
1.03k stars 180 forks source link

建议可在上游地址前加上 udp:// 来强制使用 UDP DNS #153

Closed ShadowOfTheDay closed 3 months ago

ShadowOfTheDay commented 4 months ago

原因:如 chinadns-ng 同时监听 TCP 和 UDP 端口,但如果上游 DNS 服务器仅支持 UDP 查询(有些公共服务器如此,或者像我的上游 DNS 服务器为仅监听 UDP 的本地 DoH 服务器),则通过 TCP 查询(有些智能设备似乎默认如此)会报错:

Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: 2024-03-26 00:27:15 I [server.zig:207 service_tcp] new connection:11 from 127.0.0.1#56070
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: 2024-03-26 00:27:15 I [server.zig:303 QueryLog.query] query(id:20536, tag:none, qtype:1, 'broker.mina.mi.com') from 127.0.0.1#56070
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: 2024-03-26 00:27:15 I [server.zig:342 QueryLog.forward] forward query(qid:170, from:tcp, 'broker.mina.mi.com') to china group
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: 2024-03-26 00:27:15 I [Upstream.zig:490 Group.send] forward query(qid:170, from:tcp) to upstream tcpin://127.0.0.1#5054
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: 2024-03-26 00:27:15 I [Upstream.zig:490 Group.send] forward query(qid:170, from:tcp) to upstream tcpin://127.0.0.1#5053
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: 2024-03-26 00:27:15 E [Upstream.zig:148 _send_tcp] connect(19, 'tcpin://127.0.0.1#5054') failed: (111) Connection refused
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: 2024-03-26 00:27:15 E [Upstream.zig:148 _send_tcp] connect(20, 'tcpin://127.0.0.1#5053') failed: (111) Connection refused

另外,根据查询方的传入协议来选择与上游的通信协议这一设定与 bind-addr 如不指定监听协议则默认监听 TCP + UDP 这两点结合起来可能会导致上述错误,所以是否考虑更改默认选项以避免新用户遇到上述问题?

zfl9 commented 4 months ago

强制使用 udp 上游在技术上没问题,而且最开始我确实准备支持 1.1.1.1tcp://1.1.1.1udp://1.1.1.1 三种形式的上游地址,但随着编码的深入,我意识到强制 udp 上游会有逻辑问题,于是在 2024.03.07 版本发布前几天,移除了 udp:// 特性。

先说一些背景知识:

为什么强制 udp 上游会有逻辑上的问题,假设一个客户端(resolver)查询 qq.com:

zfl9 commented 4 months ago

根据查询方的传入协议来选择与上游的通信协议

原因很简单,就是要让 chinadns-ng <=> upstream 的 msg size 上限 >= resolver <=> chinadns-ng 的 msg size 上限

(这个其实也是 dnsmasq 的默认行为,以前我不太能理解为什么这么做,现在理解了)

只有在这个前提下,才不会出现 tcp fallback 时的逻辑问题。



补充一点,一个 upstream group 可以有多种不同的协议的 dns 服务器,假设其中有 udp:// 上游、tcp:// 上游;此时从 tcp 收到一个查询,转发给了这组 upstream,因为同一 upstream group 内的响应采用的是抢答机制(使用最先响应的结果),通常 udp 响应是最快的(因为没有其他协议的那些开销),如果恰好该消息因为 size 问题被 truncated 了,那么也会让 resolver 感到困惑(从 tcp 查询中得到了 TC 结果)。

zfl9 commented 4 months ago

另外,我个人不建议关闭 chinadns-ng 的 TCP 监听(如 --bind-port 65353@udp 参数,只监听 UDP 端口),防止出现 tcp fallback 时,resolver 无法与 chinadns-ng 进行通信,导致连接失败。

zfl9 commented 4 months ago

如果上游 DNS 服务器仅支持 UDP 查询(有些公共服务器如此,或者像我的上游 DNS 服务器为仅监听 UDP 的本地 DoH 服务器

公共 DNS 应该都支持 TCP 查询了,毕竟这是 RFC 的强制要求,就连 ISP 的 DNS 服务器也支持。

你说本地 DoH 服务器不支持,那我就没得办法了,应该有相关监听参数的,开一下 TCP 监听就好了。

zfl9 commented 4 months ago

所以是否考虑更改默认选项以避免新用户遇到上述问题?

默认要监听 TCP 和 UDP 是 RFC 的要求,个人不建议关闭 TCP 监听。

ShadowOfTheDay commented 3 months ago

感谢 @zfl9 详细的说明!现在明白这样设计的考量了。 是我使用的 http_dns_proxy 不支持 TCP 查询,搜到之前作者还在犹豫是否要支持:aarond10/https_dns_proxy#131 其他的 DoH proxy 像 dnscrypt-proxy 是支持 TCP 查询的,不过懒得折腾换了… ~在 chinadns-ng 原生支持 DoH 之前,我暂时的 workaround 就是让 chinadns-ng 只监听 UDP 端口了。~ Edit: 好吧,关闭 TCP 监听果然还是会有问题,有时候 DoH 查询的 msg size 不知为什么就会很大,超过 512 Bytes。还是把 http-dns-proxy 换成支持 TCP 查询的 dnscrypt-proxy2了。

windmsn commented 3 months ago

强制使用 udp 上游在技术上没问题,而且最开始我确实准备支持 1.1.1.1tcp://1.1.1.1udp://1.1.1.1 三种形式的上游地址,但随着编码的深入,我意识到强制 udp 上游会有逻辑问题,于是在 2024.03.07 版本发布前几天,移除了 udp:// 特性。

先说一些背景知识:

  • 大部分 dns 流量仍然走 udp 查询,因为比 tcp 查询开销更低,延迟也更低,技术实现上也简单。
  • dns over udp 虽好,但有个经典的问题,那就是 msg size 有限制,在没有 EDNS 的情况下,是 512 字节,即使有 EDNS,也不过 1000+ 字节,虽然理论上 EDNS udp bufsize 是 4096,但因为超出以太网 MTU 值(1500 字节)太多,丢包风险很高,所以现实世界用的最多的是 1232 字节。
  • 如果 msg size 超过了 udp 对端的接收缓冲区大小,则会在 header 中设置 TC 标志位,然后 resolver(操作系统/libc)观察到消息已截断,于是通过 tcp 发起一个相同的查询,以便获得完整的 dns msg。

为什么强制 udp 上游会有逻辑上的问题,假设一个客户端(resolver)查询 qq.com:

  • resolver 默认使用 udp 与 chinadns-ng 通信,发送 query msg 给 chinadns-ng
  • chinadns-ng 收到查询后,转发给 upstream,假设配置了强制 udp 上游,如 udp://1.1.1.1
  • 该 response msg 因为 size 过大,被 truncate 了,chinadns-ng 将这个 msg 返回给 resolver
  • resolver 观察到 TC 标志,于是丢弃该消息,通过 tcp 与 chinadns-ng 通信,重新发起该查询
  • chinadns-ng 收到查询后,转发给 upstream,因为强制 udp 上游,该消息仍然逃不过被 TC 的命运
  • resolver 再次收到带 TC 标志的响应消息,而且是从 tcp 查询中获得的,理论上 tcp 查询是不存在截断的(resolver 感到困惑),会导致该域名永远解析不成功

今天使用有这么一个问题,chinadns-ng的trust upstream为dns2tcp 运行时配置如下

2024-04-10 16:07:02 I [main.zig:117 main] local listen addr: ::#5354@tcp+udp
2024-04-10 16:07:02 I [main.zig:117 main] china upstream: tcpin://211.136.192.6
2024-04-10 16:07:02 I [main.zig:117 main] china upstream: udpin://211.136.192.6
2024-04-10 16:07:02 I [main.zig:117 main] china upstream: tcpin://120.196.165.24
2024-04-10 16:07:02 I [main.zig:117 main] china upstream: udpin://120.196.165.24
2024-04-10 16:07:02 I [main.zig:117 main] trust upstream: tcpin://127.0.0.1#5353
2024-04-10 16:07:02 I [main.zig:117 main] trust upstream: udpin://127.0.0.1#5353
2024-04-10 16:07:02 I [main.zig:117 main] trust upstream: tcpin://127.0.0.1#5352
2024-04-10 16:07:02 I [main.zig:117 main] trust upstream: udpin://127.0.0.1#5352

当客户端使用以下命令查询时 dig @127.0.0.1 -p5354 +tcp www.youtube.com chinadns-ng报以下错误

2024-04-10 16:07:11 I [server.zig:203 service_tcp] new connection:7 from ::ffff:10.0.0.21#62838
2024-04-10 16:07:11 I [server.zig:302 QueryLog.query] query(id:39920, tag:gfw, qtype:28, 'www.youtube.com') from ::ffff:10.0.0.21#62838
2024-04-10 16:07:11 I [server.zig:349 QueryLog.forward] forward query(qid:1, from:tcp, 'www.youtube.com') to trust group
2024-04-10 16:07:11 I [Upstream.zig:490 Group.send] forward query(qid:1, from:tcp) to upstream tcpin://127.0.0.1#5353
2024-04-10 16:07:11 I [Upstream.zig:490 Group.send] forward query(qid:1, from:tcp) to upstream tcpin://127.0.0.1#5352
2024-04-10 16:07:11 E [Upstream.zig:148 _send_tcp] connect(8, 'tcpin://127.0.0.1#5353') failed: (146) Connection refused
2024-04-10 16:07:11 E [Upstream.zig:148 _send_tcp] connect(9, 'tcpin://127.0.0.1#5352') failed: (146) Connection refused

客户端的dig报以下错误

[root@ManTou:/root]#dig @127.0.0.1 -p5354 +tcp www.youtube.com
;; Connection to 127.0.0.1#5354(127.0.0.1) for www.youtube.com failed: connection refused.

同样地。国内网站使用tcp查询时因为运营商dns不支持tcp而导至查询失败连接超时

2024-04-10 16:42:37 I [server.zig:302 QueryLog.query] query(id:53791, tag:chn, qtype:1, 'www.taobao.com') from ::ffff:127.0.0.1#35362
2024-04-10 16:42:37 I [server.zig:349 QueryLog.forward] forward query(qid:33, from:tcp, 'www.taobao.com') to china group
2024-04-10 16:42:37 I [Upstream.zig:490 Group.send] forward query(qid:33, from:tcp) to upstream tcpin://211.136.192.6
2024-04-10 16:42:37 I [Upstream.zig:490 Group.send] forward query(qid:33, from:tcp) to upstream tcpin://120.196.165.24
2024-04-10 16:42:42 W [server.zig:827 on_timeout] query(qid:33, id:53791, tag:chn) from tcp://::ffff:127.0.0.1#35362 [timeout]
2024-04-10 16:42:42 E [Upstream.zig:148 _send_tcp] connect(12, 'tcpin://211.136.192.6') failed: (145) Operation timed out
2024-04-10 16:42:42 E [Upstream.zig:148 _send_tcp] connect(13, 'tcpin://120.196.165.24') failed: (145) Operation timed out

[root@ManTou:/root]#dig @127.0.0.1 -p5354 +tcp www.taobao.com
; <<>> DiG 9.9.9-P3 <<>> @127.0.0.1 -p5354 +tcp www.taobao.com
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached

虽然看似upstream有udpin和tcpin,但是客户端只要指定了tcp查询,chinadns-ng就只forward tcp,并不会使用udp,

zfl9 commented 3 months ago

见 2024.04.13 版本。