Skip to content

[Bug] v1.19.20 在某些情况下会出现 dns 超时问题 #2560

@CallMeR

Description

@CallMeR

验证步骤

  • 我已经阅读了 文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
  • 我仔细看过 文档 并未解决问题
  • 我已在 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 收到超时错误。

重现方式

如上

日志

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions