-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Description
验证步骤
- 我已经阅读了 文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
- 我仔细看过 文档 并未解决问题
- 我已在 Issue Tracker 中寻找过我要提出的问题,并且没有找到
- 我是中文用户,而非其他语言用户
- 我已经使用最新的 Alpha 分支版本测试过,问题依旧存在
- 我提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
- 我提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器或者堆砌大量对于复现无用的配置等。
- 我提供了完整的日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
- 我直接使用 Mihomo 命令行程序重现了错误,而不是使用其他工具或脚本。
操作系统
Linux
系统版本
Debian13
Mihomo 版本
v1.19.20
配置文件
router-dns: &router-dns
- 10.0.0.1 # 主路由 DNS
domestic-dns: &domestic-dns
- 223.5.5.5 # AliDNS
dox-dns: &dox-dns
- tls://dot.pub
- quic://dns.alidns.com
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
oversea-dns: &oversea-dns # 海外主 DNS, 用于解析日常访问所需域名
- 127.0.0.1:8053 # SmartDNS - globalDNS描述
测试环境整体结构:服务器内网 ip 10.0.0.4 / fe80::4 ,Dnsmasq 监听 53 端口,Dnsmasq 唯一上游 127.0.0.1:6053 到内部 smartdns ,smartdns 开 6053 默认组,8053 海外组,smartdns 开启 audit 便于观察具体请求路径以及行为。
初步观察:下游设备向 10.0.0.4 发送 dns 请求时,被 mihomo 拦截走了内部处理流程,不会通过 Dnsmasq + smartdns;下游设备向 fe80::4 发送 dns 请求时,未被 mihomo 拦截,经过 Dnsmasq 到达 smartdns 的默认组,由于默认组仍然为普通 udp 上游,会再次被 mihomo 拦截并走内部处理流程。
异常情况:在某次重启服务器验证 dns 整体初始化阶段时,发现服务器可正常启动,但下游设备无法再通过 10.0.0.4 获得任何解析结果,现象为多次超时,10.0.0.4 无响应时,尝试向 fe80::4 发送请求,发现正确到达 smartdns ,但仍然查询超时,关闭 mihomo 后,dns 查询恢复正常;单独回退mihomo 到 v1.19.19 问题消失。
借助 xxx 初步分析:v1.19.20 引入的 DNS 客户端重构(尤其是 DoT 连接池及 UDP/TCP 客户端逻辑)可能存在 资源泄漏缺陷 。
在网络延迟或高并发场景下,Mihomo 内部会积累大量僵死 Goroutine,耗尽系统资源,导致无法响应外部的请求。
在 v1.19.20 版本中:
- 新增 dns/dot.go : 引入了独立的 DNS-over-TLS (DoT) 客户端,包含自定义连接池。
- 修改 dns/client.go : 简化了原有的客户端逻辑,移除了 TLS 处理,专注于 UDP/TCP。
目前设计依赖 context.Context 来控制超时(如 100ms),但底层依赖库 github.com/miekg/dns 的核心方法 ExchangeWithConn 是 阻塞式 的,且设计上不支持 Context 取消 。
// 存在于 dns/dot.go 和 dns/client.go
go func() {
// 阻塞调用,默认超时 5秒
// 即使上层 Context 在 100ms 后取消,此行代码仍会继续阻塞运行
msg, _, err = dClient.ExchangeWithConn(m, dConn)
ch <- result{msg, err}
}()
select {
case <-ctx.Done():
return nil, ctx.Err() // 函数返回,但 Goroutine 泄漏!
case res := <-ch:
return res.msg, res.err
}- 触发 : 发起请求 -> Mihomo 拦截 -> Mihomo 向上游发起请求(但 smartdns 上游未初始化完成此时会挂起或循环)。
- 阻塞 : 如果上游响应稍慢(>100ms),Dnsmasq 或 Mihomo 上层逻辑会超时取消 Context。
- 泄漏 : 虽然逻辑上“取消”了,但底层的 Goroutine 仍在等待回包(最长 5秒)。
- 雪崩 : 在高并发下(如 DNS 初始化阶段),这些“僵尸” Goroutine 迅速累积,耗尽文件描述符(FD)或 socket 缓冲区。
- 现象 : Mihomo 无资源处理新请求 -> Dnsmasq 收到超时错误。
重现方式
如上