在家庭或小微企业网络中,使用 OpenWrt 软路由配合 OpenClash 实现网络分流是非常主流的方案。然而,部分用户在拨号(PPPoE)环境下可能会遇到偶尔网络突然完全中断(国内外网页均无法打开)的问题。此时,关闭 OpenClash 国内网络即刻恢复,重启 OpenClash 后国外网络也恢复正常。
本文将基于一次真实的断网事件,结合 OpenWrt 的内核日志、PVE(Proxmox VE)虚拟化平台的监控图表,以及对具体软件版本的追踪,详细拆解这一故障的排查思路、根本原因及解决方法。
一、 故障现象描述
- 物理环境:光猫桥接,由 PVE 虚拟机中的 OpenWrt 进行 PPPoE 拨号上网。
- 软件环境:OpenWrt 运行 OpenClash 插件,版本为
luci-app-openclash (0.47.116-r6)。 - 异常症状:网络无预警突然完全中断(局域网下所有设备国内外网页均无法打开)。
临时恢复手段:
- 在 OpenWrt 后台关闭 OpenClash,国内网络立即恢复。
- 再次开启 OpenClash,国外访问也恢复。
二、 故障排查步骤
为了找出断网的深层原因,我们需要在网络中断发生时(先不要重启系统或插件)收集两部分关键数据:系统内核日志和宿主机监控图表。
1. 内核日志分析:寻找系统底层的异常警告
登录 OpenWrt 终端,查看内核日志(dmesg)或系统日志,发现在断网时刻(约 21:15 前后)系统抛出了大量的内存泄露和进程终止警告。
关键日志 ①:OOM-Killer 被频繁唤醒
[21:15:11] kern.warn: [691123.690785] WEB[5] invoked oom-killer: gfp_mask=0x140cca, order=0 ...
[21:15:11] kern.err: [691123.691187] Out of memory: Killed process 7978 (clash) total-vm:1464708kB, anon-rss:75896kB ...
[21:15:50] kern.err: [691162.657278] Out of memory: Killed process 14070 (clash) total-vm:1448216kB, anon-rss:93580kB ...
[21:16:18] kern.err: [691190.024307] Out of memory: Killed process 31323 (clash) total-vm:1382608kB, anon-rss:87708kB ...分析:内核日志明确给出了 Out of memory(内存耗尽)警告。Linux 内核为了防止整机崩溃,被迫激活了 OOM-Killer 机制,开始强制杀掉占用内存最大的进程。被杀掉的进程正是 clash。
关键日志 ②:惊人的进程堆积
在 OOM 发生前,系统列出了当时的所有进程状态:
[ pid ] uid tgid total_vm rss rss_anon rss_file rss_shmem ... name
[ 7978] 0 7978 366177 19007 18974 33 0 ... clash
[ 16183] 0 16183 345716 11939 11922 17 0 ... clash
[ 19429] 0 19429 345652 11233 11215 18 0 ... clash
[ 11836] 0 11836 345668 20655 20642 13 0 ... clash
[ 13522] 0 13522 345652 21454 21454 0 0 ... clash
[ 25395] 0 25395 362054 20952 20946 6 0 ... clash
...(多达 21 个独立的 clash 进程同时存在)- 异常点:日志中同时出现了 20 多个独立的
clash进程(它们的TGID与PID相同,说明是完全独立的实例,而非单个程序派生出的线程)。 - 每个
clash进程占用了 40MB 至 90MB 不等的物理内存,累加后直接将系统分配的 2.0 GiB 内存全部吃满,且 ZRAM Swap(交换分区)也几乎被完全榨干(Free swap = 96kB)。
2. 宿主机(PVE)监控图表印证
从 PVE 的虚拟机监控图表(时间范围:20:55 - 21:50)中,可以发现数据与上述日志在时间线上高度一致:
| 监控指标 | 故障前 (~21:10) | 故障发生中 (21:15 - 21:35) |
|---|---|---|
| 内存使用率 | 长期死卡在 2.0 GiB 红线上 | 21:15 分突发式、阶梯式下跌 |
| 网络流量 | 保持 1.2M - 1.6M 正常波形 | 瞬间跌落归零,呈死寂直线 |
| 磁盘 IO | 平稳 | 出现 22 MB/s 的读取大高峰 |
| CPU 利用率 | 低位平稳 | 飙升至 90% 的异常高峰 |
| CPU/IO PSI | 平稳 | 发生严重红区停滞(Stall) |
- 内存的阶梯式下跌:对应了 OOM-Killer 连续强制杀掉残留
clash进程的时刻。 - 磁盘 IO 与 CPU 的异常暴涨:在 OOM 发生前夕,由于内存耗尽,系统疯狂地在物理内存与 Swap 分区之间进行数据换入换出(Linux 称为系统抖动 Thrashing),产生了高额的 CPU 和 IO 开销,甚至导致系统压力停滞(PSI)飙升,软路由处于半瘫痪状态。
- 网络流量归零:由于真正负责转发和 DNS 解析的活跃 Clash 进程被杀掉,而路由表的重定向和
tun网卡仍然残留,导致所有进出路由器的流量掉入“黑洞”,直至用户在21:35重启服务。
三、 根本原因分析:代码逻辑缺陷与版本时间差
1. 软件停止逻辑中的 Bug 机制
在 OpenWrt 拨号环境下,运营商通常会定期(例如 24/48 小时)强制断开连接并重新分配 IP。当 WAN 口 IP 发生变化、或者守护进程检测到网络异常时,OpenClash 会尝试重启服务。
然而,在旧版 OpenClash 插件的 stop_service()(服务停止)控制脚本中,存在一个逻辑缺陷:
# OpenClash 停止逻辑简化示例
procd_kill "openclash"
for i in $(seq 1 10); do
procd_running "openclash" >/dev/null && sleep 1 || break
done
# 漏洞发生在这里:
if procd_running "openclash" >/dev/null; then
kill -9 $(pidof clash) 2>/dev/null || true
fi逻辑缺陷拆解:
- 脚本首先调用
procd_kill终止 OpenClash 托管的服务。 - 由于 Procd 响应并终止了服务,
procd_running "openclash"会立刻返回false(表示服务未在运行)。 - 因为返回了
false,下面的整个if代码块直接被跳过。 - 最终导致的结果是:原本作为保底机制的
kill -9 $(pidof clash)根本没有机会执行。 - 那些因为网络变动、或在 Procd 停止前就已经脱离控制的旧
clash进程彻底变成了“孤儿进程”留在内存中。每一次网络重拨或服务重启,都会在后台累积一个新的进程,直到内存被彻底吃满并引发断网。
2. 软件版本追踪:为何 0.47.116-r6 恰好踩坑?
在 OpenWrt 系统中,通过 opkg 检索,发现当前安装的版本为 luci-app-openclash (0.47.116-r6),且系统提示“没有可用更新”。
根据 OpenClash 官方开源社区的代码提交历史,可以清晰地解释该版本为何踩坑:
0.47.116版本发布时间:该版本编译发布于 2026年7月6/7日。- Bug 提报与立项:针对这一“重启导致 Clash 孤儿进程堆积(GitHub Issue #5215)”的特定 Bug,是由社区用户在 2026年7月8日 提交并确立的。
- 修复引入:官方随后在后续版本(例如
0.47.117/0.47.118以后,直至 7月15日的0.47.128)中,才重构了停止清理逻辑,移除了不合理的procd_running守卫,彻底修复了该 Bug。
因此,0.47.116-r6 版本恰好处于该严重 Bug 尚未被发现并修复的“灰色窗口期”。
四、 解决方案
由于 OpenWrt 的默认软件源(Feed)往往由固件编译者预设,若云端仓库未及时拉取最新代码,opkg 软件中心便会认为 0.47.116-r6 是最新版。要解决此问题,可以采用以下三种解决方案:
方案 A:手动下载并上传最新 .ipk(推荐)
这是最彻底的解决方式。您可以跳过服务商默认的软件源,手动下载官方已修复的版本进行覆盖安装。
- 访问 OpenClash GitHub Releases 页面,下载最新版的 IPK 文件(例如
luci-app-openclash_0.47.128_all.ipk或更新的版本)。 - 登录您的 OpenWrt 后台,导航至
系统->软件包页面。 - 点击页面右侧的
上传软件包...按钮,选择下载好的.ipk文件上传并安装。安装完成后,控制脚本中的进程管理 Bug 将被彻底修复。
方案 B:手动修改控制脚本(快速,无需下载)
如果您因为环境限制不便更换插件版本,可以直接修改现有的控制脚本:
- 通过 SSH 登录 OpenWrt 终端。
- 编辑脚本:
vi /etc/init.d/openclash。 定位到
stop_service()函数部分,找到类似如下的代码:if procd_running "openclash" >/dev/null; then for pid in $(pidof clash 2>/dev/null); do kill -9 "$pid" 2>/dev/null || true done fi修改方法:去除外部的
if限制,直接使其无条件清理残留进程(删除if和fi,保留 kill 命令即可):for pid in $(pidof clash 2>/dev/null); do kill -9 "$pid" 2>/dev/null || true done- 保存并退出编辑器。
方案 C:临时过渡措施(计划任务定时清理)
作为排查过渡期间的临时防线,您可以在 OpenWrt 的 系统 -> 计划任务 中加入一行定时任务(例如每天凌晨 4 点强制清理一次残留并重启服务),保证系统状态每日保持纯净:
0 4 * * * killall -9 clash && /etc/init.d/openclash restart五、 结语
在排查软路由偶发性网络故障时,单一的症状(如“国内外网页都打不开”)往往极具误导性。通过将系统内核日志(定位到 OOM)、宿主机虚拟化监控(定位到资源吞吐与网络归零节点)以及开源社区版本时间线(精准定位 Bug 引入与修复窗口)结合在一起,可以非常高效地还原故障现场并根治问题。
面对类似问题,检查后台是否存在残留的进程累积,往往能为您提供出人意料的突破口。