饿饭cc云-高速云端数据加密传输服务!

mihomo TProxy UDP 内存泄漏排查:识别 listenLocalConn 堆积与临时缓解方案

更新于 2026-10-23 · efancloud.cyou

简答:mihomo 开启 TProxy UDP 后内存持续上涨,主要源于每个新 UDP 流创建的 listenLocalConn 读取协程缺少空闲超时,导致协程与本地 UDP socket 以每小时约 2000 个的速度驻留内存。可通过比对 UDP 活跃连接数与 pprof 协程栈确认问题,并在正式版升级前采用关闭 TProxy UDP、切换 TUN、配置定时重启或合并 60 秒超时补丁的方式缓解。

定义:mihomo TProxy UDP 内存泄漏是指在 Linux 透明代理(TProxy)入站启用 UDP 转发时,处理回包写入的本地 UDP 连接(listenLocalConn)因未设置读取超时(read deadline)而无法自动释放,引发 goroutine、文件描述符(FD)及进程驻留内存随运行时间线性累加的现象。

先试这一步:30 秒速查卡与 3 条验证命令

如果你的 /connections 活跃连接只有几十个,但系统里的 UDP socket 与协程却以每小时约 2000 个的速度只增不减,基本就是命中了 官方仓库中的用户 Issue #3343。根据该报告的实测记录,该泄漏在 v1.19.30 中已存在(内存增长约 27 MB/小时),在已确认受影响的 v1.19.32 中增长速度加快至约 3 到 5 倍(约 75–150 MB/小时),更早版本或其余未验证版本的受影响范围目前未知。

在动手修改配置前,先对照下表用 30 秒完成初步定位:

速查项检查方法命中 #3343 泄漏的特征下一步操作
1. 是否启用 TProxy + UDP核对配置文件 tproxy-port 或 type: tproxy入站开启了 TProxy 且显式设置 udp: true若未开启则排除此问题;已开启请看第 2 项
2. Socket 与连接数是否背离比对 API 的 UDP 连接数与系统 ss -u 计数API 显示活跃 UDP 极少,但系统 UDP socket 数以千计高度疑似泄漏,继续执行下方第 3 步确认
3. 协程是否堆在 listenLocalConn开启 log-level: debug 后查 pprof 栈数百至数千个协程卡在 listenLocalConn.func1基本可判定命中该问题,直接跳转至第四节选缓解方案

下面直接在终端执行 3 条命令获取验证数据(假设 Mihomo 的控制端口为 127.0.0.1:9090,密钥为 your_secret,实际端口与密钥以客户端设置页为准):

1. 核对当前 Mihomo 核心版本与内存占用

由于不同客户端界面路径存在差异(入口以当前版本界面为准),可通过 external-controller 接口直接查询内核版本与实时内存状态:

curl -s -H "Authorization: Bearer your_secret" http://127.0.0.1:9090/version
curl -s -H "Authorization: Bearer your_secret" http://127.0.0.1:9090/memory

预期结果与判定标准:第一个命令返回如 {"meta":true,"version":"v1.19.32"};第二个命令输出 inuse(当前使用内存字节数)。在 官方仓库中的用户 Issue #3343 的实测案例中,短短 30 分钟内 inuse 从 126 MB 涨至 163 MB,长期运行约每小时增长 27 MB(v1.19.30)至 75–150 MB(v1.19.32),直至触发容器内存上限挂死。

2. 比对活跃 UDP 代理连接数与系统 UDP Socket 句柄数

由于 /connections 接口默认包含 TCP 与 UDP 全部连接,为保证对比维度一致,我们只统计其中 "network":"udp" 的条目,并在 root 权限下使用 ss -p 统计操作系统层面该进程持有的 UDP 套接字数量:

curl -s -H "Authorization: Bearer your_secret" http://127.0.0.1:9090/connections | grep -o '"network":"udp"' | wc -l
sudo ss -u -a -p | grep mihomo | wc -l

预期结果与判定标准:正常情况下,两者的数量级应当接近。而在 官方仓库中的用户 Issue #3343 报告的场景中,/connections 总连接数在任意时刻仅有 50 个左右,但 3 小时内创建的 7087 个 listenLocalConn 仅有 173 个正常退出,导致系统内滞留数千个未关闭的 UDP socket。

3. 检查 pprof 协程堆栈是否卡在 packet.go

注意:Mihomo 的 /debug/pprof 路由通常仅在配置文件设置了 log-level: debug 时才会由 external-controller 挂载,且请求时同样需要携带鉴权头:

curl -s -H "Authorization: Bearer your_secret" "http://127.0.0.1:9090/debug/pprof/goroutine?debug=1" | grep -A 5 "listenLocalConn"

预期结果与判定标准:在 官方仓库中的用户 Issue #3343 实测中,总协程数在 1 小时内从 331 飙升至 2207,其中高达 2079 个协程停留在 github.com/metacubex/mihomo/listener/tproxy.listenLocalConn.func1(位于 listener/tproxy/packet.go:103)且从不回落。若你的输出呈现相同特征,基本可判定命中该问题。

核心指标对照:如何区分真泄漏、Go 堆缓存与正常高并发

在排查 Mihomo 内存告警时,许多运维人员容易将 Go 语言运行时的垃圾回收(GC)延迟归还机制误判为内存泄漏,或者将 P2P 软件引发的 UDP 洪泛误认为内核缺陷。我们在 官方仓库中的用户 Issue #1438 等历史讨论中也能看到,旧版本(如 v1.18.4)或特定平台(如 Android)的内存占用包含多种复杂诱因,不能一概直接等同于当前的 #3343 泄漏。

作者推论:为了在生产或家庭网关环境中做出准确诊断,可以将观测点拆解为三个独立层次——TProxy 入站的本地回写 UDP 连接(LocalConn)、负责阻塞读取的 reader goroutine,以及进程整体的 RSS/Heap 表现。下表紧跟上一节的测试命令,结合 官方仓库中的用户 Issue #3343 的实测数据与作者日常排障经验值,帮你快速对号入座:

观测维度与采集工具TProxy UDP 真实泄漏 (#3343 特征)Go GC 堆内存未及时归还局域网 UDP 流量洪泛 (如 BT/QUIC)正常参考基线 (来源说明)
/connections 活跃连接数平稳,Issue 实测任意时刻约 50 个平稳,随实际请求正常波动极高,瞬间可达数千个 UDP 会话轻度使用约数十至百余个(作者经验值,仅供参考)
sudo ss -u -a -p 进程 UDP Socket持续线性累加,Issue 实测每小时新增约 2000 个平稳,与活跃 UDP 连接数基本匹配随并发飙升,但停止下载后数分钟内回落与活跃 UDP 连接数同量级
/proc/<pid>/fd 句柄总数随未关闭 socket 持续单向上涨保持稳定,无单向增长趋势高峰期激增,流量停止后回落常见默认软限制内(作者经验值,仅供参考)
pprof/goroutine 栈分布大量堆积在 listenLocalConn.func1(实测 1 小时达 2079 个)无单一函数异常堆积,总数稳定广泛分布在转发与规则匹配,流量停则消退Issue 实测初始值约 331 个(其中 TCP 转发占 48 个)

如果你还需要直接从 Linux 内核的 /proc 文件系统核实进程打开的文件描述符与物理内存,可以先获取进程 PID 再查看状态:

pidof mihomo
sudo ls /proc/$(pidof mihomo)/fd | wc -l
grep -E "VmRSS|VmSize" /proc/$(pidof mihomo)/status

判定标准:当 /proc/<pid>/fd 的计数随小时数单调递增,且开启 log-level: debug 后如 官方仓库中的用户 Issue #3343 所记录——日志在 3 小时内打印了 7087 次 TProxy listenLocalConn rAddr=... lAddr=... 却仅打印 173 次 TProxy local conn listener exit 时,即可排除 Go GC 缓存假象,确认为本地套接字泄漏。

复现与配置核查:检查是否启用了 TProxy UDP 入站

该问题仅在同时满足“启用 TProxy 透明代理入站”且“开启了 UDP 转发支持”时才会触发。根据 mihomo 官方文档 Inbound Configuration 与 mihomo 官方文档 tproxy listener 说明,用户在配置文件中通常通过顶层字段 tproxy-port 或 listeners 列表来定义 TProxy 入站。

以下是用于核查或复现该现象的完整最小化配置文件(假设保存为 /etc/mihomo/config.yaml,端口统一以 7894 和控制端口 9090 为例,实际以客户端设置页为准):

mixed-port: 7890
allow-lan: true
mode: rule
log-level: debug
external-controller: 127.0.0.1:9090
secret: "your_secret"

dns:
  enable: true
  listen: 0.0.0.0:1053
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver:
    - 223.5.5.5

listeners:
  - name: tproxy-in
    type: tproxy
    port: 7894
    listen: 0.0.0.0
    udp: true

proxies: []

proxy-groups:
  - name: PROXY
    type: select
    proxies:
      - DIRECT

rules:
  - MATCH,PROXY

单变量对照与压测验证思路:

  1. 区分流量入口:在测试环境中,若你在本机执行带 -x http://127.0.0.1:7890 的 curl 命令,流量走的是混合代理端口(Mixed Port),不会经过 TProxy 监听器,因此不会触发 listenLocalConn。要复现或验证 TProxy 行为,必须如 官方仓库中的用户 Issue #3343 所述,将 Mihomo 部署在局域网前端,通过网关的 iptables 或 nftables TPROXY 规则接管局域网设备(手机、电脑)产生的日常短连接 UDP 流量(如 DNS、NTP 和 QUIC)。
  2. 触发高频 UDP 短流并与 TCP 对照:在受透明代理接管的局域网客户端上,发起普通的 HTTP/1.1、HTTP/2 请求,以及较大体积的 POST 请求(走 TCP),同时连续发起 DNS 查询或 QUIC 请求(走 UDP)作为对照:
curl --http1.1 -I https://www.example.com
curl --http2 -I https://www.example.com
curl -X POST -d "test_payload_data" https://www.example.com
dig @8.8.8.8 www.example.com +retry=0

预期结果:根据 官方仓库中的用户 Issue #3343 对源码(listener/tproxy/packet.go)的引用分析,前三条 TCP 流量在 Mihomo 中由 tunnel.handleSocket 处理,完成后会正常释放;而第四条命令及各类 QUIC 流量走 UDP 协议,每个新 UDP 流都会调用 natTable.AddForLocalConn 存储连接并启动 listenLocalConn 协程,由于该协程仅在遇到 net.ErrClosed 时返回且没有任何机制清理空闲条目,导致本地连接在会话结束后依然滞留在 natTable 中。

修复进度与正式发版前的 4 种临时缓解方案

截至 2026-10-23,我们需要明确区分“社区已提交修复代码”与“你手中的发行版已包含修复”这两个状态:

在等待包含该修复的正式版本发布期间,你可以根据自身的网络拓扑选择以下 4 种临时止损方案(每次只改一处进行单变量验证):

方案 A:在监听器中临时关闭 TProxy 的 UDP 转发(最稳妥)

如果局域网设备主要依赖 TCP 协议访问网页,可将前文 /etc/mihomo/config.yaml 中的 listeners 段落修改为关闭 UDP(完整文件其余部分保持上一节内容不变):

listeners:
  - name: tproxy-in
    type: tproxy
    port: 7894
    listen: 0.0.0.0
    udp: false

同时在防火墙(iptables/nftables)中注释掉针对 UDP 的 TPROXY 挂载规则。副作用:外网 QUIC(HTTP/3)、语音通话及部分游戏联机等依赖透明代理的 UDP 流量将改为直连或回退至 TCP(例如浏览器遇到 UDP 443 不通会自动平滑回退到基于 TCP 的 HTTP/2)。

方案 B:从 TProxy 切换为 TUN 模式接管流量

根据 mihomo 官方文档 Inbound Configuration,透明代理除 TProxy 外还可采用 TUN 虚拟网卡模式。注意:以下进阶参数需 Mihomo 内核支持,且 tun.stack 仅支持 system、gvisor、mixed 三个合法值。你可以移除 listeners 中的 TProxy 配置,在完整的 /etc/mihomo/config.yaml 中替换为 TUN 入站(并加上 dns-hijack 以确保 DNS 请求被正常接管):

mixed-port: 7890
allow-lan: true
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
secret: "your_secret"

dns:
  enable: true
  listen: 0.0.0.0:1053
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver:
    - 223.5.5.5

tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  dns-hijack:
    - any:53

proxies: []

proxy-groups:
  - name: PROXY
    type: select
    proxies:
      - DIRECT

rules:
  - MATCH,PROXY

网关场景必要项与副作用:若将该配置用于局域网旁路由或主路由网关,除了上述配置外,还必须在 Linux 宿主机开启内核 IPv4/IPv6 转发(如 sysctl -w net.ipv4.ip_forward=1),并将局域网设备的默认网关和 DNS 指向该主机 IP;若仅用于本机代理则直接生效。切换至 TUN 前请务必清空原有的防火墙 TPROXY 规则,避免数据包环路;在部分低功耗设备上 TUN 模式的 CPU 开销可能略高于 TProxy。

方案 C:拉取修复分支自行编译并验证 60 秒回收

如果你具备 Go 编译环境并选用了自行拉取 官方仓库中的修复 PR #3344 源码编译二进制文件的方式,替换内核重启后,请按以下步骤验证 60 秒空闲回收(idle cleanup)是否真正生效:

  1. 开启 log-level: debug,通过受控客户端触发数次 UDP 请求,确认日志打印出 TProxy listenLocalConn。
  2. 停止客户端的所有网络活动,静置等待 65 秒(略大于补丁设定的 60 秒空闲阈值)。
  3. 查看日志是否如期输出 TProxy local conn listener exit,并再次执行第一节中的 sudo ss -u -a -p | grep mihomo | wc -l 命令。
  4. 判定标准:静置 65 秒后,刚才新增的 UDP socket 数量应回落至测试前基线,且 goroutine 计数不再维持高位,证明补丁已生效。

方案 D:保留 TProxy UDP,配置 systemd 内存上限与定时重启

如果业务场景既不能关闭 TProxy UDP,短期内也不便切换至 TUN 或自行编译内核,可通过 systemd 的内存保护机制配合 cron 定时重启来防止宿主机或容器因 OOM 死机。首先创建或编辑完整的服务单元文件 /etc/systemd/system/mihomo.service:

[Unit]
Description=Mihomo Daemon
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
Restart=always
RestartSec=3
MemoryMax=512M

[Install]
WantedBy=multi-user.target

保存后重载 systemd 并添加每日凌晨 4 点的定时重启任务:

sudo systemctl daemon-reload
sudo systemctl restart mihomo
(crontab -l 2>/dev/null; echo "0 4 * * * /bin/systemctl restart mihomo") | sudo crontab -

预期结果与副作用:当 Mihomo 进程内存达到 512M 上限时,systemd 会主动终止并花 3 秒拉起新进程,或者在每天凌晨 4 点自动清空一次累积的协程与 socket。副作用:触发重启的瞬间会中断局域网内正在进行的长连接(如正在下载的文件、SSH 会话或在线游戏)。

补充指南:向官方提交 Issue 前必须准备的核验材料

假如你在采取上述措施或升级到后续版本后,仍然遇到内存异常上涨,计划向开发团队反馈,请务必先阅读 官方仓库中的 Issue #1049 提交规范说明。官方明确要求所有问题报告必须直接通过 Mihomo 命令行核心复现,不接受仅由外围图形客户端、第三方启动脚本或魔改插件引发的故障报告。

为确保你的反馈不被直接关闭,请在关闭所有外围脚本、仅用单行命令启动 Mihomo 核心的前提下,采集并附上以下 5 项标准材料:

免责声明:本站为第三方客户端与网络排查使用指南站,非饿饭cc云官方网站;文中提及饿饭cc云仅作背景说明,不涉及任何未核实的价格、活动或服务状态。

常见问题

只要使用了 mihomo 就会触发这个 TProxy UDP 内存泄漏吗?

不会。该问题仅在配置文件中启用了 TProxy 入站(如设置了 tproxy-port 或 type: tproxy 的 listener)且同时开启了 UDP 支持(udp: true),并配合系统防火墙将局域网 UDP 流量透明转发给 mihomo 时才会触发。仅使用 HTTP/SOCKS 代理端口或 TUN 模式的用户不受此特定路径影响。

为什么在面板里点击“关闭所有连接”后,泄漏的内存和 UDP socket 还是没有释放?

根据官方仓库中的用户 Issue #3343 对源码的引用说明,每个新 UDP 流创建的本地连接通过 natTable.AddForLocalConn 存储在本地 NAT 表中,且其读取协程仅在遇到 net.ErrClosed 时才会返回。作者推论:面板的常规连接清理主要作用于隧道层的活跃会话(/connections),并未触发底层 TProxy natTable 中闲置本地连接的关闭逻辑,因此无法唤醒并销毁这些阻塞在无超时读取调用上的协程。

在等待包含 PR #3344 的正式版发布前,如果必须保留 TProxy UDP,有什么折中办法?

如果业务场景不能关闭 UDP 透明代理且暂不便切换至 TUN 模式,建议按本文第四节的“方案 D”为运行 mihomo 的 systemd 服务配置内存上限保护(如 MemoryMax=512M),并结合定时任务(cron)在每日凌晨网络空闲时段执行一次重启(注意重启瞬间会中断现有活动连接),或者拉取 PR #3344 的分支代码自行编译替换内核。

相关阅读

参考资料

  1. [Bug] TProxy UDP: listenLocalConn goroutines and sockets leak (~2000 per hour) · Issue #3343 · MetaCubeX/mihomo
  2. tproxy - mihomo docs
  3. inbound configuration - mihomo docs
  4. [Bug] Memory usage · Issue #1438 · MetaCubeX/mihomo