在家庭或小微企业网络中,使用 OpenWrt 软路由配合 OpenClash 实现网络分流是非常主流的方案。然而,部分用户在拨号(PPPoE)环境下可能会遇到偶尔网络突然完全中断(国内外网页均无法打开)的问题。此时,关闭 OpenClash 国内网络即刻恢复,重启 OpenClash 后国外网络也恢复正常。

本文将基于一次真实的断网事件,结合 OpenWrt 的内核日志、PVE(Proxmox VE)虚拟化平台的监控图表,以及对具体软件版本的追踪,详细拆解这一故障的排查思路、根本原因及解决方法。


一、 故障现象描述

  • 物理环境:光猫桥接,由 PVE 虚拟机中的 OpenWrt 进行 PPPoE 拨号上网。
  • 软件环境:OpenWrt 运行 OpenClash 插件,版本为 luci-app-openclash (0.47.116-r6)
  • 异常症状:网络无预警突然完全中断(局域网下所有设备国内外网页均无法打开)。
  • 临时恢复手段

    1. 在 OpenWrt 后台关闭 OpenClash,国内网络立即恢复。
    2. 再次开启 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 进程(它们的 TGIDPID 相同,说明是完全独立的实例,而非单个程序派生出的线程)。
  • 每个 clash 进程占用了 40MB 至 90MB 不等的物理内存,累加后直接将系统分配的 2.0 GiB 内存全部吃满,且 ZRAM Swap(交换分区)也几乎被完全榨干(Free swap = 96kB)。

2. 宿主机(PVE)监控图表印证

从 PVE 的虚拟机监控图表(时间范围:20:55 - 21:50)中,可以发现数据与上述日志在时间线上高度一致:
PVE监控图表

监控指标故障前 (~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

逻辑缺陷拆解

  1. 脚本首先调用 procd_kill 终止 OpenClash 托管的服务。
  2. 由于 Procd 响应并终止了服务,procd_running "openclash" 会立刻返回 false(表示服务未在运行)。
  3. 因为返回了 false,下面的整个 if 代码块直接被跳过。
  4. 最终导致的结果是:原本作为保底机制的 kill -9 $(pidof clash) 根本没有机会执行。
  5. 那些因为网络变动、或在 Procd 停止前就已经脱离控制的旧 clash 进程彻底变成了“孤儿进程”留在内存中。每一次网络重拨或服务重启,都会在后台累积一个新的进程,直到内存被彻底吃满并引发断网。

2. 软件版本追踪:为何 0.47.116-r6 恰好踩坑?

在 OpenWrt 系统中,通过 opkg 检索,发现当前安装的版本为 luci-app-openclash (0.47.116-r6),且系统提示“没有可用更新”。
86-2.webp

根据 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(推荐)

这是最彻底的解决方式。您可以跳过服务商默认的软件源,手动下载官方已修复的版本进行覆盖安装。

  1. 访问 OpenClash GitHub Releases 页面,下载最新版的 IPK 文件(例如 luci-app-openclash_0.47.128_all.ipk 或更新的版本)。
  2. 登录您的 OpenWrt 后台,导航至 系统 -> 软件包 页面。
  3. 点击页面右侧的 上传软件包... 按钮,选择下载好的 .ipk 文件上传并安装。安装完成后,控制脚本中的进程管理 Bug 将被彻底修复。

方案 B:手动修改控制脚本(快速,无需下载)

如果您因为环境限制不便更换插件版本,可以直接修改现有的控制脚本:

  1. 通过 SSH 登录 OpenWrt 终端。
  2. 编辑脚本:vi /etc/init.d/openclash
  3. 定位到 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
  4. 修改方法:去除外部的 if 限制,直接使其无条件清理残留进程(删除 iffi,保留 kill 命令即可):

    for pid in $(pidof clash 2>/dev/null); do
        kill -9 "$pid" 2>/dev/null || true
    done
  5. 保存并退出编辑器。

方案 C:临时过渡措施(计划任务定时清理)

作为排查过渡期间的临时防线,您可以在 OpenWrt 的 系统 -> 计划任务 中加入一行定时任务(例如每天凌晨 4 点强制清理一次残留并重启服务),保证系统状态每日保持纯净:

0 4 * * * killall -9 clash && /etc/init.d/openclash restart

五、 结语

在排查软路由偶发性网络故障时,单一的症状(如“国内外网页都打不开”)往往极具误导性。通过将系统内核日志(定位到 OOM)宿主机虚拟化监控(定位到资源吞吐与网络归零节点)以及开源社区版本时间线(精准定位 Bug 引入与修复窗口)结合在一起,可以非常高效地还原故障现场并根治问题。

面对类似问题,检查后台是否存在残留的进程累积,往往能为您提供出人意料的突破口。

最后修改:2026 年 07 月 16 日
如果觉得我的文章对你有用,请随意赞赏