OpenWrt 路由器上跑 OpenClash:手动补内核、双路由和 fake-ip 排查

这次折腾从一个很普通的问题开始:OpenWrt 路由器里已经装了 OpenClash,但内核一直下不下来,页面上反复点更新也很慢。后来内核补上了,手机和另一台电脑都能访问外网,只有 Mac 不行。最后根因落在两个地方:路由器里曾经选错了内核架构;Mac 上本机 Clash Verge 的 TUN 又和路由器 OpenClash 的 fake-ip 撞到了一起。

这类问题很容易被一句「路由器代理不通」盖过去。实际拆开看,里面至少有四层:OpenWrt 插件、Mihomo 内核、家庭网络拓扑、客户端自己的 DNS 和 TUN。任何一层抢了 DNS 或路由,表面现象都可能只是浏览器打不开外网。

写这篇是为了把这次经验整理成一套可复用的排查路径:先把 OpenClash 跑起来,再确认流量真的进了路由器,最后处理客户端本机代理和路由器代理之间的冲突。

几个概念先对齐

OpenClash 是跑在 OpenWrt 上的 LuCI 插件。它负责把配置、订阅、规则、DNS、透明代理和运行状态整合进路由器管理页面。真正处理代理协议、规则匹配、DNS fake-ip 和 TUN 的,是 Clash / Mihomo 这类 core。

OpenClash release 页面会提供插件包,例如 .ipk.apk。Mihomo core 通常从 MetaCubeX/mihomo releases选择对应 CPU 架构的二进制。写这篇时,OpenClash 页面最新能看到 v0.47.110,Mihomo 仓库最新 release 是 v1.19.27。版本号会变,实际操作前应该重新看 release 或用 GitHub API 查一次。

这里的 fake-ip 指代理 DNS 给域名临时分配一个保留网段里的假 IP,例如 198.18.x.x。应用连接这个假 IP 后,Mihomo 根据内部映射知道它原本对应哪个域名,再按规则选择直连或走节点。

TUN 是虚拟网卡模式。开启后,系统路由会把更多流量送进虚拟网卡,由 Mihomo 接住连接并重新出站。路由器上的 OpenClash 可以开 TUN,本机 Clash Verge 也可以开 TUN;两边同时接管同一段 fake-ip 时,就会出现本文后半段的冲突。

本次网络拓扑

这次不是所有设备都直接连在 OpenWrt 的 192.168.100.0/24 网段。OpenWrt 下面又接了一个下级路由,下级路由负责发 Wi-Fi 和 192.168.3.0/24 地址。手机截图里能看到设备拿到的是 192.168.3.x,Mac 也是 192.168.3.36,网关是 192.168.3.1

双层路由下的 OpenClash 拓扑

这种拓扑里,客户端能访问 192.168.100.1 只说明下级路由到 OpenWrt 的管理地址可达,不能直接证明所有外网流量都被 OpenClash 接管。真正要看的有两个点:

  • 客户端 DNS 最终是否会进 OpenWrt / OpenClash,而不是被手动 DNS 绕走。
  • 客户端访问 fake-ip 时,系统路由是否会把流量交给下级路由和 OpenWrt,而不是被本机 TUN 截走。

如果下级路由做了 NAT,OpenWrt 日志里可能只看到下级路由的 WAN 地址,例如 192.168.100.x,而不是每台手机或电脑自己的 192.168.3.x 地址。这不一定是问题,只是 NAT 后的正常表现。

手动补 OpenClash 内核

路由器下载 GitHub 资源慢时,可以在电脑上下载 core,再通过 SSH 传到 OpenWrt。关键是先确认路由器 CPU 架构,不要按电脑架构选文件。

在 macOS / zsh 上先连路由器检查:

ssh root@192.168.100.1 'uname -m; opkg print-architecture; ls -la /etc/openclash/core 2>/dev/null || true'

这台路由器返回的是:

aarch64
arch aarch64_cortex-a53 10

所以应该选择 linux-arm64。当时 OpenClash 配置里却是 core_version='linux-386',这就是第一个坑:插件和订阅页面看起来都有,但 core 架构错了,服务就可能一直起不来。

常见架构可以按下面粗略对应:

路由器 uname -m / opkg 架构 Mihomo / OpenClash core 名称
aarch64 / arm64 / aarch64_cortex-a53 linux-arm64
armv7l linux-armv7
x86_64 linux-amd64,老 CPU 可试 linux-amd64-compatible
mipsel / mipsle linux-mipsle-softfloatlinux-mipsle-hardfloat
mips linux-mips-softfloatlinux-mips-hardfloat

写作时 Mihomo 最新 release 是 v1.19.27,ARM64 文件名是 mihomo-linux-arm64-v1.19.27.gz。实际执行时先查最新版本:

curl -s https://api.github.com/repos/MetaCubeX/mihomo/releases/latest \
  | jq -r '.tag_name'

下载、解压并传到路由器:

cd ~/Downloads

curl -L -o clash_meta.gz \
  https://github.com/MetaCubeX/mihomo/releases/download/v1.19.27/mihomo-linux-arm64-v1.19.27.gz

gzip -dc clash_meta.gz > clash_meta
chmod +x clash_meta

scp ./clash_meta root@192.168.100.1:/tmp/clash_meta.new

在路由器上备份配置、替换 core、修正 OpenClash 记录的架构:

ssh root@192.168.100.1 '
set -eu
TS=$(date +%Y%m%d-%H%M%S)

mkdir -p /etc/openclash/core
cp -a /etc/config/openclash /etc/config/openclash.bak.$TS 2>/dev/null || true

if [ -f /etc/openclash/core/clash_meta ]; then
  cp -a /etc/openclash/core/clash_meta /etc/openclash/core/clash_meta.bak.$TS
fi

mv /tmp/clash_meta.new /etc/openclash/core/clash_meta
chown root:root /etc/openclash/core/clash_meta
chmod 755 /etc/openclash/core/clash_meta

uci set openclash.config.core_version="linux-arm64"
uci commit openclash

/etc/openclash/core/clash_meta -v
'

验证输出应该类似:

Mihomo Meta v1.19.27 linux arm64

这里不要省掉 chmod 755。二进制文件放对了但没有执行权限,后面的现象仍然会像“OpenClash 起不来”。

启动后先看这几项

core 能跑只是第一步。OpenClash 启动后,先用 SSH 看服务、监听端口和 DNS,而不是只盯 LuCI 页面上的状态文字。

ssh root@192.168.100.1 '
echo "# status"
/etc/init.d/openclash status 2>/dev/null || true
ps | grep -Ei "[o]penclash|[c]lash|[m]ihomo" || true

echo "# config"
for k in enable config_path core_version en_mode operation_mode proxy_mode router_self_proxy; do
  printf "%s=" "$k"
  uci get openclash.config.$k 2>/dev/null || true
done

echo "# listeners"
netstat -lntup 2>/dev/null | grep -E "(:53|:789|:9090|:7874)" || true

echo "# logs"
tail -120 /tmp/openclash.log 2>/dev/null || true
'

这次跑起来后的关键状态是:

enable=1
core_version=linux-arm64
en_mode=fake-ip-tun
operation_mode=fake-ip
proxy_mode=rule

监听端口里能看到 789078917893789578749090。它们大致对应 HTTP / SOCKS / mixed / tproxy / DNS / external controller。不同配置端口可能不同,按 uci show openclash 和生成后的配置文件为准。

还要确认路由器自己能访问外网:

ssh root@192.168.100.1 '
ping -c 3 -W 2 223.5.5.5 || true
ping -c 3 -W 3 baidu.com || true
'

如果 IP 能 ping 通、域名不行,优先查 DNS;如果路由器自己都不能 ping 外网 IP,先查 WAN、默认路由和上级光猫 / 主路由。

双层路由下怎么判断客户端有没有进 OpenClash

客户端在 192.168.3.x 网段时,可以先在客户端上看 DHCP 和 DNS。macOS / zsh 下:

ipconfig getifaddr en0
ipconfig getoption en0 router
ipconfig getoption en0 domain_name_server
networksetup -getdnsservers Wi-Fi

这次修正后,Mac 上看到的是:

ip=192.168.3.36
router=192.168.3.1
dns=192.168.3.1

networksetup -getdnsservers Wi-Fi 返回 “There aren’t any DNS Servers set on Wi-Fi.”,说明 Wi-Fi 没有手动钉死 DNS,系统按 DHCP 走下级路由。

之前手动设过 114.114.114.114,这会把一部分查询绕出 OpenClash。双层路由下最稳的默认状态是:客户端 DNS 自动,交给下级路由;下级路由再按它的配置转给上级 OpenWrt 或上游 DNS。如果希望强制走 OpenClash,也可以在下级路由 DHCP 里把 DNS 下发成 OpenWrt 或下级路由自身,但不要让单台客户端手动写一个公共 DNS。

DNS 和连通性可以这样测:

dig @192.168.3.1 www.google.com +short +time=2 +tries=1
dig @192.168.100.1 www.google.com +short +time=2 +tries=1
curl --interface en0 -I --max-time 10 https://www.google.com/generate_204

如果 OpenClash 使用 fake-ip,dig 返回 198.18.x.x 是正常的。真正要看的是后面的 HTTPS 请求能不能完成 TLS 握手并返回 204

本次修正后的结果是:

HTTP/2 204

YouTube 和 GitHub 也能拿到 HTTP/2 200。这说明外网访问已经正常。

这次最隐蔽的问题:本机 Clash Verge TUN 抢了 fake-ip

一开始 Mac 的 Google 请求失败在 TLS 阶段:

www.google.com -> 198.18.0.60
LibreSSL: sslv3 alert handshake failure

当时路由器 OpenClash 已经能处理其他设备的流量,日志里也能看到手机和其他客户端访问 Google、Instagram、Twitter。Mac 特别失败,不像是节点或订阅整体不可用。

继续看 Mac 的路由表:

route -n get 198.18.0.60
netstat -rn -f inet | grep -E '198\.18|utun|default'
ps aux | egrep -i 'clash|verge|mihomo|SmartVPN|VPN' | grep -v egrep

当时能看到本机 Clash Verge 的 verge-mihomo 在运行,并且本机有 utun1024

route to: 198.18.0.60
gateway: 198.18.0.1
interface: utun1024

这就解释了为什么只有这台 Mac 不通。路由器 OpenClash 的 DNS 返回了 198.18.x.x fake-ip,但 Mac 本机 Clash Verge 的 TUN 也在接管 198.18.x.x。连接还没出 Wi-Fi,就先被本机 TUN 吃掉了。

路由器 fake-ip 和本机 TUN 的冲突

关掉 Clash Verge 的 TUN 后,utun1024 和那批 198.18 专用路由消失,Google 访问马上恢复:

* Host www.google.com:443 was resolved.
* IPv4: 198.18.0.77
* Connected to www.google.com (198.18.0.77) port 443
* SSL connection using TLSv1.3
< HTTP/2 204

这里容易误伤另一个进程:SmartVPN / iOA。当前环境里 SmartVPN 还在运行,它使用的是 utun4 和另一段 fake-ip,例如 100.12.0.0/22。它不是这次 Google / YouTube 外网失败的主因。真正冲突的是 Clash Verge 本机 TUN 对 198.18.x.x 的接管。

判断口径可以简单一点:

  • 如果其他设备都能通过路由器 OpenClash 上网,只有这台电脑不行,先查这台电脑自己的 DNS、系统代理、TUN 和路由。
  • 如果 dig 返回 198.18.x.x,而 route -n get 198.18.x.x 指向本机某个 utun,优先怀疑本机 Clash / Mihomo / Surge 一类 TUN 抢走了路由。
  • 如果公司 iOA / SmartVPN 仍在运行,但它的 fake-ip 网段和路由器 OpenClash 不重合,不要把它当成第一嫌疑。

常见故障按层排

这次排障有一个稳定顺序:先看路由器能不能跑,再看客户端有没有把流量交给路由器,最后看本机有没有更高优先级的代理抢流量。

OpenClash 一直 inactive

先查 core,而不是先怀疑订阅:

ssh root@192.168.100.1 '
uname -m
opkg print-architecture 2>/dev/null || true
uci get openclash.config.core_version 2>/dev/null || true
ls -lh /etc/openclash/core 2>/dev/null || true
/etc/openclash/core/clash_meta -v 2>/dev/null || true
'

如果路由器是 aarch64,OpenClash 却写着 linux-386,先修正架构和 /etc/openclash/core/clash_meta。架构错了,订阅再刷新也没用。

路由器能上网,客户端不能

先区分“路由器自己没网”和“客户端没有进路由器代理”:

ssh root@192.168.100.1 '
ping -c 3 -W 2 223.5.5.5 || true
ping -c 3 -W 3 baidu.com || true
tail -120 /tmp/openclash.log 2>/dev/null || true
'

如果路由器能 ping IP 和域名,OpenClash 日志也有其他设备流量,问题多半在当前客户端。看客户端是否拿到正确网关和 DNS:

ipconfig getifaddr en0
ipconfig getoption en0 router
ipconfig getoption en0 domain_name_server
networksetup -getdnsservers Wi-Fi
scutil --proxy

macOS 上 networksetup -getdnsservers Wi-Fi 如果返回某个公共 DNS,例如 114.114.114.114,说明 Wi-Fi 上有手动 DNS。它可能绕开路由器的 OpenClash DNS。改回自动后,重新断开连接或刷新 DNS 再测。

fake-ip 能解析,但 HTTPS 握手失败

先看 fake-ip 路由:

dig www.google.com +short +time=2 +tries=1
route -n get 198.18.0.77
netstat -rn -f inet | grep -E '198\.18|198\.19|utun'

198.18.0.77 只是本次会话里的例子,实际按 dig 返回替换。如果目标 fake-ip 被路由到本机 Clash Verge 的 utun,就关掉本机 TUN 模式,或者至少让本机 TUN 不接管路由器 OpenClash 使用的 fake-ip 网段。

明确代理端口返回 407

这次用下面的命令测试路由器代理端口时,返回过 407 Proxy Authentication Required

curl -k -I --max-time 10 -x http://192.168.100.1:7890 https://www.google.com/generate_204

这不等于 OpenClash 没启动。它说明代理端口能连到,但当前配置要求认证,或者订阅生成的配置加了 authentication。透明代理是否正常,要继续看 DNS、TUN、OpenClash 日志和普通访问结果,不要只靠显式 HTTP proxy 端口下结论。

OpenClash 插件怎么更新

core 和插件是两件事。core 是 /etc/openclash/core/clash_meta 里的可执行文件;插件是 LuCI 页面和脚本。只缺 core 时,不需要重装插件。要更新 OpenClash 插件时,再下载 .ipk.apk 包安装。

OpenClash release 页面给了按包管理器下载最新版的思路:先请求 GitHub latest release,再从 assets 里取 .ipk.apk。在传统 OpenWrt 上通常是 .ipk

ssh root@192.168.100.1 '
set -eu
opkg update
opkg install bash dnsmasq-full curl ca-bundle ip-full ruby ruby-yaml kmod-tun kmod-inet-diag unzip kmod-nft-tproxy luci-compat luci luci-base

curl -L --retry 2 https://api.github.com/repos/vernesong/OpenClash/releases/latest -o /tmp/openclash_version
download_url=$(jsonfilter -i /tmp/openclash_version -e "@.assets[*].browser_download_url" | grep "\.ipk$")
curl -L --retry 2 "$download_url" -o /tmp/openclash.ipk
opkg install /tmp/openclash.ipk
'

OpenWrt snapshot 或新版固件如果使用 apk 包管理器,就选 release 里的 .apk 包,并用 apk add --allow-untrusted 安装。更新插件前建议先备份 /etc/config/openclash 和当前配置文件;更新后不要立刻改一堆选项,先确认服务、端口、日志、DNS 和订阅状态都正常。

路由器有 Docker,可以玩什么

这次没有把 Docker 和 OpenClash 混在一起调。代理还没有稳定前,不建议先起一堆会改 DNS、路由或防火墙的容器,否则排障会多一层变量。等 OpenClash 稳了,路由器 Docker 可以从轻量服务开始:

  • AdGuard Home / MosDNS 类 DNS 服务:适合做广告过滤和本地域名,但要明确谁负责 53 端口,不要和 OpenClash DNS 抢入口。
  • Uptime Kuma:监控家里的 NAS、路由器、订阅面板、节点测速页面,资源占用相对轻。
  • Home Assistant / MQTT:做智能家居中枢,前提是路由器内存和存储够用。
  • filebrowser / Syncthing:做临时文件入口或轻量同步,只监听内网会更稳。

原则也很简单:OpenClash 先负责路由器网络主线;Docker 服务尽量只服务内网;凡是会碰 DNS、DHCP、TUN、iptables / nftables 的容器,等网络链路稳定后再慢慢加。

我以后会按这个顺序查

这类家庭网络代理问题最怕在一个层里打转。之后再遇到类似情况,我会按这个顺序排:

  • 先确认路由器 CPU 架构和 core 文件:uname -mopkg print-architecture/etc/openclash/core/clash_meta -v
  • 再确认 OpenClash 服务和监听端口:/etc/init.d/openclash statusnetstat -lntup/tmp/openclash.log
  • 再确认路由器自己能出网:先 ping IP,再 ping 域名。
  • 再确认客户端 DHCP 和 DNS:网关是谁,DNS 是自动还是手动公共 DNS。
  • 再确认 fake-ip 路由:dig 返回的 198.18.x.x 最终走 Wi-Fi,还是走本机某个 TUN。
  • 最后才看订阅、节点测速和规则组选择。节点不可用会影响外网,但它通常不会解释“其他设备都行,只有一台电脑不行”。

总结下来,这次真正有价值的是分层排查:OpenClash 起不来,先查 core 架构;双层路由下能打开 192.168.100.1,还要继续确认 DNS 和透明代理是否走对;路由器返回 fake-ip 后,客户端本机 TUN 仍然可能抢在路由器前面把连接截走。把这几层拆开,问题就会从“玄学断网”变回几条可验证的路由和 DNS 记录。