一、前言
在运行 OpenWrt 的路由器上跑 Clash 网关代理有许多现成的方案,但是因为一些原因,那些方案我都不喜欢。
在 Clash v1.8.0 版本更新之前,我将就用着缝合各家方案的 Shell 脚本来运行 Clash,虽然不是特别稳定,但日子还算勉强过得去。不幸的是,Clash v1.8.0 版本之后开发者去除了 redir-host 模式的远端 DNS 解析,我在并未仔细研究更新日志的情况下例行升级了 Clash Premium Core,从此我的网关代理就没稳定过……终端翻墙的日子持续了好几个月,在这期间我尝试过各种办法,但终究不够稳定,甚至我找回旧版本,想用回原来较稳定的方案都玄学地失败了。
前些日子(1 月中旬的样子)我在例行翻阅 Clash 项目的 Issue 和 Discussion 时,偶然发现一位朋友提出了我从未见过的方案,如下图:
![]() |
来源位置:Issue#1358 |
我当时立马就在 OpenWrt 上安装了 VPN Policy Routing 这个组件,但是可能因为心情过于浮躁,不得其门而入,只能遗憾放弃,继续苦哈哈地研究 Shell 脚本启动和 iptables。——实际上还是因为我太菜了,才会被这一点点问题搞得身心俱疲。
前几天我再次尝试这个 VPN Policy Routing 方案时,不知怎么福至心灵,突然就找到了窍门——当然其实逻辑非常简单,各位看官大手子们一定一看到界面就知道该怎么弄了。在“简单”研究了两天之后,我认定这个方案还算稳定,故写出来以作记录,或许能稍微造福到后来人(如果有人看的话嘤嘤嘤)。
二、环境
其他环境不保证成功,OpenWrt 各种固件差别很大,基于 Linux 让它的环境很容易受到各种预装软件/组件/插件的影响(例如某些固件默认劫持 53 端口的 TCP/UDP 流量到路由器)。
关于第三点为什么是 Policy Based Routing 而不是前文提到的 VPN Policy Routing,那是因为后者已经停止开发,而由同一个开发者开发的前者是后者的继任者,二者的 LuCI 完全一致,功能上基本相同,甚至配置文件可以通用。
它的作用正如它的名字,即按照策略将流量路由到不同的接口,我们只要为 Clash Tun 创建一个网络接口,就可以按照自行设定的策略来将需要的流量路由到 Clash Tun 中——对于我这种完全搞不明白计算机网络和 iptables 的辣鸡来说用处还是很大的。
三、过程
3.1 添加 PBR 开发者的仓库并安装部分组件和依赖
由于 PBR 尚未被添加到 OpenWrt 官方仓库,所以需要添加开发者的个人仓库。(Tips:如果在意这一点完全可以安装官方仓库中的 vpn-policy-routing,二者几乎没有差别。)
添加开发者的仓库可以参照开发者的文档,以下是我自己的用法:echo -e -n 'untrusted comment: OpenWrt usign key of Stan Grishin\nRWR//HUXxMwMVnx7fESOKO7x8XoW4/dRidJPjt91hAAU2L59mYvHy0Fa\n' > /etc/opkg/keys/7ffc7517c4cc0c56
echo 'src/gz stangri_repo https://repo.openwrt.melmac.net' >> /etc/opkg/customfeeds.conf
opkg update
opkg install pbr luci-app-pbr kmod-tun curl
假如你所在的网络环境很差,可以考虑更换为国内的 opkg 仓库源,清华镜像站的更换帮助。
可能有人想知道这行命令都安装了什么:
![]() |
(点开应该可以看到大图) |
其中 libcurl、libwolfssl 和 libnghttp2 是 curl 引入的依赖。
安装过程中 pbr 有两行报错,暂且不用管它,先进入下一步。3.2 启动 Clash Tun 并手动创建接口
mixed-port: 1080
mode: rule
log-level: info
dns:
enable: true
listen: 0.0.0.0:1053
enhanced-mode: fake-ip
nameserver:
- tls://dot.pub:853
tun:
enable: true
stack: gvisor
/etc/clash/clash -d /etc/clash
之后导航到 Services -> Policy Based Routing,Enable 之后更改中间两项如图,保存并应用。
注意,你应该按照自己的需求事先创建好 WAN/LAN,如果此时在 Policies 的下拉框中看不到 WAN/LAN,则需要在 Advanced Configuration 中的 Supported Interfaces 上自行添加接口(此处填写接口应该是大小写敏感的,但 OpenWrt 中经常按全大写显示,需要留意)。
然后删除掉默认的三条示例规则,点击 Start,界面应该如下所示:
这时候在终端窗口按下 Ctrl+C,结束 Clash 进程,这个时候 Clash 创建的 utun 设备会消失,手动创建的 Clash 接口随之下线,按照刚才的设定,接口下线时 PBR 不强制按策略路由——所以刷新一下浏览器,可以看到我们创建的 GoClash 策略的目标接口已经变成了 WAN/LAN/IGNORE,反正不会再是 Clash,这时候网络又恢复正常了。
为了不危害家里的网络,我们需要限制一下要走 Clash Tun 的设备。以只允许当前使用的电脑被路由为例,我们需要在 GoClash 策略上的的 Local addresses / devices 一栏填上当前电脑的 IP,比如:
注意注意注意!这时需要点击一次 Restart,否则流量不会被路由到 Clash Tun。
3.4 处理 DNS 问题
1.首先要确认你其他设备 DNS 已经指向 OpenWrt 路由器,确认你所用的 OpenWrt 固件没有在防火墙用户规则里劫持 53 端口的流量;
2. 其次,无论是从上游自动获取也好,手动设定也好,至少要保证 WAN/LAN 接口上有 DNS,并关闭固件内其他所有的 DNS 方案,保证是 dnsmasq 在监听 53 端口;
3. 然后导航到 Network -> DHCP and DNS -> General Settings,在 DNS Forwardings 填上 Clash 的 DNS,比如按前文配置的 127.0.0.1#1053,用井号代替冒号;
使用 dnsmasq 的一点优点,在于它的 DNS 有 fallback(按照我的粗浅理解和实际体验),即当 Clash 进程被杀了、爆炸了,127.0.0.1#1053 获取不到 DNS 结果的时候,dnsmasq 会向在 WAN/LAN 配置的 DNS 查询结果,加上 PBR 会将路由改回 WAN/LAN,这时候家里的网络并不会跟随 Clash 一起爆炸(由于终端设备 DNS 缓存的问题,可能要多刷新几次)。
缺点在于,有时候 dnsmasq 会小概率抽风,跳过 Clash 的 DNS 而向 WAN/LAN 配置的 DNS 查询域名结果,就会没法正常代理。
——将全部的 DNS 查询指向 Clash 会有一些额外的问题,我们后文再讨论。
3.5 创建 Clash 的控制脚本、开机启动
#!/bin/sh /etc/rc.common
# /etc/init.d/clash
USE_PROCD=1
START=99
STOP=15
# CLASH_BIN=/etc/clash/clash
# CONFIG_PATH=/etc/clash
# CONFIG=/etc/clash/config.yaml
LOG=/tmp/clash.log
start_service()
{
rm $LOG
touch $LOG
procd_open_instance
procd_set_param command /bin/sh -c "/etc/clash/clash -d /etc/clash >> /tmp/clash.log"
procd_set_param stderr 1
procd_close_instance
restart_pbr
}
restart_pbr()
{
/etc/init.d/pbr restart
}
/etc/init.d/clash start
/etc/init.d/clash stop
/etc/init.d/clash restart
3.6 终端设备的访问控制(路由和 DNS)、绕过私网地址、端口转发问题
同样是抛砖引玉,我给出我的办法。
1. 在 LAN 接口将 DHCP 分配的起点改到 130 以上,限制 125 以内自己决定(我这边设备少,为了方便管理设定了 200,55);
2. 将你要走网关代理的设备设定静态 IP 地址,在 192.168.1.2-192.168.1.126 之间;
3. 修改之前在 PBR 上创建的 GoClash 策略,将 Local addresses / devices 一栏改成 192.168.1.0/25 (根据你的子网前缀修改)——为什么是 /25 :/25 囊括了子网前半部分 192.168.1.1-192.168.1.126 的地址,方便进行访问控制,即你手动制定到这个 IP 范围内的设备流量会被路由到 Clash Tun,而未指定的设备、新加入的设备会默认分配到这个范围之外,即默认不走网关代理;4. 新建一条 PBR 策略,命名为 Local_ips,在 Remote addresses / domains 填上 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4 ,用空格隔开,LuCI 界面会标红,但不用管他(是个小 bug),Interface 选 WAN/LAN/IGNORE(我选 IGNORE),并将这条策略上移,保证在 GoClash 策略上面的位置——这条是让私网流量绕过 Clash Tun;
5. 新建一条 PBR 策略,命名为 Ports_Fwd,根据你的需要,将你设定的(子网 /25 之内的)端口转发源设备 IP 填到 Local addresses / devices 一栏、源设备端口填到 Local ports,Interface 选 WAN/LAN/IGNORE,多个项目可以用空格隔开,但最好每个设备新建一条策略,并上移位置,保证在 GoClash 策略之上的位置——Clash Tun 会影响端口转发;
# DNS Hijack for Clash-Bypass devices
iptables -t nat -A PREROUTING ! -s 192.168.1.0/25 -p udp --dport 53 -j DNAT --to-destination 119.29.29.29 -m comment --comment "DNS Hijack Rule in Firewall Custom Rules"
——由于子网所有 DNS 请求都指向了 DNS,如果只做路由控制,不走 Clash 网关的设备请求 DNS 之后还是会得到 198.18.0.x 的 Clash Fake-IP 结果,而这个网段按照默认路由一定会走向 Clash 网关,所以需要劫持这些设备的 DNS 请求到一个正常的 DNS 之上,其中 192.168.1.0 需要按自己的子网网段改动,119.29.29.29 也可以改成其他的 DNS。
————————————————————————————————————
然后其实本文的所有步骤就走完了,其他的需求比如更新配置文件啊,更新 mmdb 文件啊,其他的访问控制啊,就需要自己解决了,毕竟 PBR 的策略还是很简洁明了的。
四、其他
这个 PBR 是一位加拿大开发者的作品,大家可以去 github 看看:@stangri
![]() |
PBR 给要去往 WAN 和 Clash 的流量分别打了标 |
![]() |
PBR 给 WAN 和 Clash 分别创建了桌子(x |
一是为了应对 utun 下线后上线而导致路由丢失的 PBR bug 而在 Clash 控制脚本中强行重启 PBR。对于这一点,PBR 有热重载机制,只要稍微改改代码就能补上这条 bug,但是我不想(不会)改代码,所以暂时就这么着吧;
二是自己劫持绕过 Clash 网关的设备的 DNS 请求。关于这一点,我也花时间研究过 dnsmqsq 的 tag 分组方法,但是研究了半天觉得在 OpenWrt 上比较难弄,如果写个脚本来处理的话,还不如一行 iptables 优雅呢,所以也就是本人能力所限,这一点也没有更好的办法。
希望这两个地方都能有更好的处理办法……
可能有大佬觉得,折腾半天也就是几行 iptables 的事,但是怎么说呢,对于我等小白来说,有一个 UI 界面比看不见摸不着的 Shell 脚本要令人安心得多~
参考链接:
https://github.com/Dreamacro/clash#premium-features
https://github.com/stangri/repo.openwrt.melmac.net
记录一下,今天 dnsmasq 把我炸了……所以我这个 DNS 方案不算靠谱。
回复删除dnsmasq 的 fallback 毕竟不是真 fallback,只是我臆想出来的功能罢了……