- 2023-07-21 更新已支持 nftable, mosdns v5
从 3 年前开始 r2s 成为了我的家庭主路由,也就是那一年 Clash 社区迅速演进,OpenClash 也逐渐成为了在 OpenWRT 上使用 Clash 的标准姿势。
但当年的 OpenClash 还没有基于 iptables 的分流功能,所有流量都经 Clash 核心转发,性能损耗过大,对 r2s 小小的 CPU 造成了大大的压力,于是我魔改了 passwall,利用 passwall 的 iptables 规则将流量分流,配合 Clash 核心,这样一直凑合用了几年。只要勤快点维护着规则,其实效果也还可以。
直到一个月前我终于下定决心把主路由换成性能稍微高一点的 r4s,同时我发现 OpenClash 也已经支持了绕过大陆 IP 的基本分流功能,加上我实在懒得维护自己的固件和魔改 passwall 了,索性用 OpenClash 的 redir-host 模式运行了一段时间,这段时间我发现 redir-host 确实对于 DNS 污染力不从心1,需要非常勤奋的维护规则才能勉强维持。我也试用过 Meta 核心,Fake-IP 和 TUN 模式,都有各自的问题,于是我又走上了自研解决方案的路。
预料到自己一定会疏于维护,这次我肯定不能再造轮子了。以下配置全部基于 OpenWRT 内已有的包,使用了 AdGuardHome,mosdns,OpenClash,经过排列组合和配置优化来达到分流和扶梯的效果。
实现效果
- 完美保留了 AdGuardHome 的各种功能
- 基于 DNS 的流量分流,国内流量绕过 Clash 核心
- 用 Fake-IP 模式来解决 DNS 污染的问题,但限制 Fake-IP 的范围,不需要代理的域名仍返回正常 IP
- 不用再费心找无污染的 DNS 服务器,使用运营商提供的 DNS 也没问题
- 因为彻底解决了 DNS 污染,可以放心缓存 DNS 请求结果,开启 AdGuardHome 的乐观缓存后,DNS 平均处理时间降到 3ms
- 完美兼容 IPv6。国内流量可正常使用 IPv6 服务。只要代理有 IPv6 出口,那国外也可正常使用。(使用 IPv6 居然还有意料之外的好处,后悔没早开)
- 兼容 BT/PT 应用,无需特殊配置也不会消耗代理流量
- 可以通过 AdGuardHome 的 Web 管理页面轻松切换内网设备是否走代理
DNS 分流
flowchart TD client[DNS 请求] -- 防火墙劫持 --> agh[AdGuardHome] agh -- 需要代理的设备 --> mosdns agh -- 无需代理的设备 --> dnsmasq mosdns -- 国内域名 --> dnsmasq mosdns -- 国外域名 --> drop_ipv6[\忽略 IPv6 请求/] mosdns -- 其它 --> fallback fallback -- 国内IP --> dnsmasq fallback -- 国外IP --> drop_ipv6 dnsmasq --> sp_dns[运营商 DNS] mosdns -- 域名白名单 --> dnsmasq mosdns -- 域名黑名单 --> drop_ipv6 drop_ipv6 --> clash[Clash Fake IP]
经过 DNS 分流后,所有需要代理的域名都分配到了 Fake IP,无需代理的域名都是由运营商 DNS 返回的最优结果。Clash 的 DNS 在 Fake IP 模式下可以无需请求网络直接返回结果,所以整体的 DNS 响应速度非常快。在处理 Fake IP 的流量时,Clash 会把 hostname 发送到远端进行 DNS 解析,也就自然不存在 DNS 污染的问题了。
流量代理分流
经过 DNS 分流以后,我们只需要一条防火墙规则,把所有目的地址是 Fake IP 的流量都转发到 Clash 核心,所有其他流量都不经转发正常通行。
OpenClash 在 Fake IP 模式下会自动帮我们添加对应的防火墙规则。但它为了防止小白误操作把其它 IP 的流量也转发到 Clash 核心了,这是没必要的,我们在自定义防火墙规则里把这条删掉就可以了。
同时由于只有 Fake IP 流量会经过代理,那么无需 DNS 解析的 IP 直连流量自然就不会经过代理了,这样就不用再担心 BT/PT 跑代理的流量了。
解决个别 IP 的代理问题
有的需要代理的 App 是直连 IP,不经过 DNS 域名解析的步骤的,目前我用到的只有一个,就是 telegram。好在 telegram 提供了它所使用的 ip-cidr 列表,我们只需要为这些 IP 单独配置防火墙规则,给它们转发到 Clash 核心。
IPv6
在这种分流方案下,可以放心开启路由器的 IPv6 支持了。因为我们在 DNS 请求阶段,把可能需要代理才能访问的 IPv6 请求已经都过滤掉了,也就不用担心代理软件对 IPv6 支持不好的问题了。
同时因为 Clash 会将 hostname 传递到远端进行解析,那么如果你的代理落地机支持 IPv6 的话,经过代理的流量就也可以走 IPv6 出口了。
内网设备分流控制
通过 AdGuardHome 的 Web 管理界面可以为局域网内不同的设备指定不同的上游 DNS 服务器。
对于需要代理的设备,把上游 DNS 服务器指定到 mosdns。对于不需代理的设备,把上游 DNS 服务器指定到 dnsmasq。这样就能做到基于设备的分流控制了。
如果内网大部分设备都需要走代理,那就把 mosdns 作为 AdGuardHome 的默认上游 DNS 服务器,然后对个别不需要走代理的设备单独配置 dnsmasq 上游。反过来也是一样的。
基于域名的黑白名单
通过 mosdns 的 query_matcher
功能,可以构建基于域名的黑白名单,保证一些你选择的域名一定通过或不通过 Clash 核心的代理。
比如开启了 IPv6 功能之后,我们就可以把 test-ipv6.com
加入到域名白名单里,这样我们就能测试自己对 IPv6 配置的怎么样了。
AdGuardHome 乐观缓存
Clash 可以在重启过程中对 Fake IP 列表进行保存,运营商 DNS 的返回结果也基本稳定,所以我们可以放心的开启 AdGuardHome 的乐观缓存功能。这时 AdGuardHome 会优先返回已过期的缓存结果然后再自己慢慢去更新缓存内容,可以大大提高内网客户端的 DNS 请求响应速度。
具体配置
上面都在讲思路,终于轮到具体的配置了。先把对应的包都安装好:luci-app-adguardhome,luci-app-mosdns,OpenClash。然后我们来一个一个的配置。
OpenClash
OpenClash 配置繁多,初始配置请自行参考 OpenClash 的 wiki,下面只说换成本文方案所需的配置。
插件设置
-模式设置
-运行模式
: 切换到Fake-IP(增强)模式
插件设置
-DNS 设置
-本地 DNS 劫持
选择禁用
插件设置
-流量控制
-绕过中国大陆 IP
取消勾选插件设置
-流量控制
-仅允许内网
开启插件设置
-IPv6 设置
这页的选项全都关闭就行了覆写设置
-常规设置
这里都不用改,只需要记住 DNS 监听,后面配置 mosdns 要用覆写设置
-DNS 设置
-自定义上游 DNS 服务器
勾选覆写设置
-DNS 设置
-追加上游 DNS
勾选覆写设置
-DNS 设置
-追加默认 DNS
勾选覆写设置
-DNS 设置
-Fake-IP 持久化
勾选覆写设置
-DNS 设置
页面下方NameServer
,FallBack
,Default-NameServer
里的 DNS 服务器全都取消勾选,我们只用运营商提供的 DNS 服务器就够了,一般运营商 DNS 都是最快的,也是 CDN 最优化的。插件设置
-GEO 数据库订阅
把GeoIP Dat
和GeoSite
这两个库的自动更新打开,都选 Loyalsoldier 的版本,这个是用来给 mosdns 用的。插件设置
-开发者选项
里,我们自定义一下防火墙规则,增加如下这些行。
FW4=$(command -v fw4)
en_mode=$(uci -q get openclash.config.en_mode)
proxy_port=$(uci -q get openclash.config.proxy_port)
if [ "$en_mode" == "fake-ip" ]; then
LOG_OUT "limit route to only fake ips with proxy port $proxy_port"
/etc/mosdns/rule/geoip2ipset.sh /etc/openclash/GeoIP.dat telegram
if [ -n "$FW4" ]; then
handle=$(nft -a list chain inet fw4 openclash | grep 'ip protocol tcp counter' | awk '{print $NF}')
LOG_OUT "deleting nft rule handle $handle"
nft delete rule inet fw4 openclash handle $handle
nft add rule inet fw4 openclash ip protocol tcp ip daddr @telegram counter redirect to $proxy_port
else
iptables -t nat -D openclash -p tcp -j REDIRECT --to-ports $proxy_port
iptables -t nat -A openclash -m set --match-set telegram dst -p tcp -j REDIRECT --to-ports $proxy_port
fi
fi
LOG_OUT "restart adguardhome"
/etc/init.d/AdGuardHome restart
其中 /etc/mosdns/rule/geoip2ipset.sh
这个脚本可以根据 GeoIP 数据库来生成对应的 ipset。内容如下,这个文件放到路由器上后,记得要执行 chmod a+x /etc/mosdns/rule/geoip2ipset.sh
给它赋予可执行权限。
#!/bin/bash
geoipfile="$1"
tag="$2"
tmpdir="/tmp/v2dat"
FW4=$(command -v fw4)
cd $(cd $(dirname $BASH_SOURCE) && pwd)
mkdir -p "$tmpdir"
filename=$(basename -- "$geoipfile")
filename="${filename%.*}"
filename="$tmpdir/${filename}_$tag.txt"
if [ "$tag" == "telegram" ]; then
wget -4 --timeout 5 -O "$filename" 'https://core.telegram.org/resources/cidr.txt'
if [ "$?" != "0" ]; then
/usr/bin/v2dat unpack geoip -o "$tmpdir" -f "$tag" "$geoipfile"
fi
else
/usr/bin/v2dat unpack geoip -o "$tmpdir" -f "$tag" "$geoipfile"
fi
if test -f "$filename"; then
if [ -n "$FW4" ]; then
nft add set inet fw4 "$tag" { type ipv4_addr\; flags interval\; auto-merge\; }
nft add set inet fw4 "${tag}6" { type ipv6_addr\; flags interval\; auto-merge\; }
nft flush set inet fw4 "$tag"
nft flush set inet fw4 "${tag}6"
fi
ipset create "$tag" hash:net -!
ipset create "${tag}6" hash:net family inet6 -!
ipset flush "$tag"
ipset flush "${tag}6"
while read p; do
if ! grep -q ":" <<< "$p"; then
if [ -n "$FW4" ]; then
nft add element inet fw4 "$tag" { "$p" }
fi
ipset add "$tag" "$p"
else
if [ -n "$FW4" ]; then
nft add element inet fw4 "${tag}6" { "$p" }
fi
ipset add "${tag}6" "$p"
fi
done <"$filename"
else
echo "$filename missing."
fi
rm -rf "$tmpdir"
mosdns
选自定义配置文件,取消 DNS 转发
的勾,然后我就直接贴配置了,注意 Clash DNS 端口要改成你自己在 OpenClash 里的配置,LAN IP-CIDR 也要改成你自己的内网配置,这里 mosdns 监听了 5335 端口。并且要在 GeoData 数据导出
里配置上我们用到的标签:GeoSite: cn, apple-cn, category-games@cn, geolocation-!cn
, GeoIP: cn
log:
level: info
file: "/tmp/mosdns.log"
include: []
plugins:
# 国内域名
- tag: geosite_cn
type: domain_set
args:
exps:
- "lan"
- "local"
- "arpa"
files:
- "/etc/mosdns/rule/whitelist.txt"
- "/var/mosdns/geosite_cn.txt"
- "/var/mosdns/geosite_apple-cn.txt"
- "/var/mosdns/geosite_category-games@cn.txt"
# 国内 IP
- tag: geoip_cn
type: ip_set
args:
files:
- "/var/mosdns/geoip_cn.txt"
# 国外域名
- tag: geosite_no_cn
type: domain_set
args:
files:
- "/etc/mosdns/rule/greylist.txt"
- "/var/mosdns/geosite_geolocation-!cn.txt"
# hosts
- tag: hosts
type: hosts
args:
files:
- "/etc/mosdns/rule/hosts.txt"
# 国内解析
- tag: local_sequence
type: sequence
args:
- exec: forward 127.0.0.1
- matches:
- has_resp
- resp_ip 192.168.1.0/24 # LAN IPCIDR
exec: ttl 1800-0
# 国外解析
- tag: remote_sequence
type: sequence
args:
- matches:
- qtype 28
exec: reject 0
- exec: forward 127.127.127.127:7874
- exec: ttl 1800-0
# 有响应终止返回
- tag: has_resp_sequence
type: sequence
args:
- matches: has_resp
exec: accept
# fallback 用本地服务器 sequence
# 返回非国内 ip 则 drop_resp
- tag: fallback_local
type: sequence
args:
- exec: $local_sequence
- matches: "!resp_ip $geoip_cn"
exec: drop_resp
# fallback 用远程服务器 sequence
- tag: fallback
type: fallback
args:
primary: fallback_local
secondary: remote_sequence
threshold: 200
always_standby: true
# 主要的运行逻辑插件
# sequence 插件中调用的插件 tag 必须在 sequence 前定义,
# 否则 sequence 找不到对应插件。
- tag: main_sequence
type: sequence
args:
# hosts
- exec: $hosts
- exec: jump has_resp_sequence
# drop https query type
- matches:
- qtype 65
exec: reject 3
# handle local ptr
- matches:
- qtype 12
exec: $local_sequence
- exec: jump has_resp_sequence
- matches:
- qname $geosite_cn
exec: $local_sequence
- exec: jump has_resp_sequence
- matches:
- qname $geosite_no_cn
exec: $remote_sequence
- exec: jump has_resp_sequence
- exec: $fallback
- tag: udp_server
type: udp_server
args:
entry: main_sequence
listen: ":5335"
AdGuardHome
在 luci 页面上,开启端口重定向,选择重定向53端口到AdGuardHome
,这里注意 AdGuardHome 本身不要监听 53 端口,把 53 端口留给 dnsmasq,AdGuardHome 设置一个其它的端口就可以了。
在 Web 管理页面上,设置
- DNS 设置
中,上游 DNS 服务器内只填写一个 mosdns 的地址 127.0.0.1:5335 #mosdns
,私人反向 DNS 服务器写上 127.0.0.1 #dnsmasq
。DNS 缓存配置里面,缓存大小看你内存大小填写,乐观缓存勾上。
对于不想走代理的设备,可以在设置
- 客户端设置
中添加,并且把上游 DNS 服务器设置成 127.0.0.1
。
私聊咨询
Subscribers Only Content Loading...