Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
ShadowOfTheDay opened this issue Mar 26, 2024 · 8 comments
Closed

Comments

@ShadowOfTheDay
Copy link

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

Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: �[32;1m2024-03-26 00:27:15 I�[0m �[1m[server.zig:207 service_tcp]�[0m new connection:11 from 127.0.0.1#56070
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: �[32;1m2024-03-26 00:27:15 I�[0m �[1m[server.zig:303 QueryLog.query]�[0m 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]: �[32;1m2024-03-26 00:27:15 I�[0m �[1m[server.zig:342 QueryLog.forward]�[0m 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]: �[32;1m2024-03-26 00:27:15 I�[0m �[1m[Upstream.zig:490 Group.send]�[0m 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]: �[32;1m2024-03-26 00:27:15 I�[0m �[1m[Upstream.zig:490 Group.send]�[0m 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]: �[35;1m2024-03-26 00:27:15 E�[0m �[1m[Upstream.zig:148 _send_tcp]�[0m connect(19, 'tcpin://127.0.0.1#5054') failed: (111) Connection refused
Tue Mar 26 08:27:15 2024 daemon.info chinadns-ng[32200]: �[35;1m2024-03-26 00:27:15 E�[0m �[1m[Upstream.zig:148 _send_tcp]�[0m connect(20, 'tcpin://127.0.0.1#5053') failed: (111) Connection refused

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

@zfl9
Copy link
Owner

zfl9 commented Mar 26, 2024

强制使用 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 感到困惑),会导致该域名永远解析不成功

@zfl9
Copy link
Owner

zfl9 commented Mar 26, 2024

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

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

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

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


  • 强制 tcp/doh/dot 等上游都可以满足这个条件,因为这些协议本质上都是 tcp 的,没有 msg size 限制。
  • 强制 udp 上游则不行,除非 resolver 与 chinadns-ng 是通过 udp 进行交互的,这也是为什么1.1.1.1这种不带协议限定的上游地址,会根据 客户端传入协议 来决定与 上游的通信协议

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

@zfl9
Copy link
Owner

zfl9 commented Mar 26, 2024

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

@zfl9
Copy link
Owner

zfl9 commented Mar 26, 2024

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

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

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

@zfl9
Copy link
Owner

zfl9 commented Mar 26, 2024

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

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

@ShadowOfTheDay
Copy link
Author

ShadowOfTheDay commented Mar 26, 2024

感谢 @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
Copy link

windmsn commented Apr 10, 2024

强制使用 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
Copy link
Owner

zfl9 commented Apr 13, 2024

见 2024.04.13 版本。

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

No branches or pull requests

3 participants