Cloudflare 的 CDN 应用广泛,包括我自己也有一些服务在用它,使用一个对中国大陆更友好的 IP 来访问 Cloudflare 可以有效提高服务质量,于是就有了优选 Cloudflare IP 的需求。CloudflareST 常用来满足这个需求,结合上篇博客中提到的 mosdns 即可实现在 DNS 层面直接优选 Cloudflare IP,优化内网设备的使用体验。
原理
- 从 GeoIP 数据库得到所有的 Cloudflare IP
- 通过 CloudflareST 测速,得到最适合我当前网络环境的 Cloudflare IP
- mosdns 接管 DNS 请求,并对请求结果在 GeoIP 数据库中查询,判断请求结果是否是 Cloudflare 的 IP
- 如果请求结果是 Cloudflare IP,mosdns 通过
hosts
的功能指定返回优选 IP - 针对上篇博客中返回 IP 不是
GeoIP:CN
时会返回 Clash Fake IP 的问题,把优选 IP 加入到response_has_local_ip
的判断条件中,确保直连 Cloudflare 优选 IP
自动运行脚本
好在 mosdns v4 的 hosts
和 response matcher
功能都支持自动加载外部文件作为 provider
,这样我们就可以通过一个简单的脚本来配置优选 IP 了。
#!/bin/bash
cd $(cd $(dirname $BASH_SOURCE) && pwd)
ip_count=4
min_speed_mb=10
max_delay=250
cloudflare_st_url="https://cf.xiu2.xyz/url"
cloudflare_st_file="/root/CloudflareST_linux_arm64/CloudflareST"
geoipfile="/etc/openclash/GeoIP.dat"
mosdns_ips_file="/etc/mosdns/rule/cloudflare_best_ips.txt"
mosdns_host_file="/etc/mosdns/rule/cloudflare_best_hosts.txt"
cloudflare_st_result_file="cloudflare_st_result.csv"
mosdns_host_prefix='regexp:^.+$'
ipset_name="cloudflare_best_ips"
cloudflare_st_ip_file="GeoIP_cloudflare.txt"
cloudflare_st_ipv4_file="cloudflare_v4.txt"
if [ ! -f "$cloudflare_st_file" ]; then
echo "CloudflareST not found"
exit 1
fi
/usr/bin/mosdns v2dat unpack-ip -o "." "$geoipfile:cloudflare"
if [ ! -f "$cloudflare_st_ip_file" ]; then
echo "CloudflareST ip file not found"
exit 1
fi
grep -v ':' $cloudflare_st_ip_file > $cloudflare_st_ipv4_file
rm -f $cloudflare_st_ip_file
iptables -t nat -nL OUTPUT --line-number | grep "$ipset_name"
if [ $? -ne 0 ]; then
ipset create $ipset_name hash:net
iptables -t nat -I OUTPUT -p tcp -m set --match-set "$ipset_name" dst -j ACCEPT
while read p; do
ip=$(echo $p | xargs)
ipset add $ipset_name $ip
done <$cloudflare_st_ipv4_file
echo "ipset created and iptables rule added"
else
echo "ipset and iptables already exists"
fi
echo "CloudflareST with ip count: $ip_count, min speed: $min_speed_mb MB/s, max_delay: $max_delay ms, url: $cloudflare_st_url"
"$cloudflare_st_file" -f "$cloudflare_st_ipv4_file" -t 1 -n 1000 -dt 5 -dn $ip_count -sl $min_speed_mb -tl $max_delay -o "$cloudflare_st_result_file" -url "$cloudflare_st_url"
rm -f "$cloudflare_st_ipv4_file"
iptables -t nat -D OUTPUT -p tcp -m set --match-set "$ipset_name" dst -j ACCEPT
ipset destroy "$ipset_name"
echo "ipset destroyed and iptables rule removed"
if [[ ! -e "$cloudflare_st_result_file" ]]; then
echo "CloudflareST result file not found"
exit 1
fi
all_ips=$(sed -n '2,$p' "$cloudflare_st_result_file" | awk -F, '{print $1}')
rm -f "$cloudflare_st_result_file"
if [[ -z "${all_ips}" ]]; then
echo "CloudflareST result IP count 0"
exit 1
fi
hosts_str=$(echo "$mosdns_host_prefix $all_ips" | tr '\n' ' ')
ips_str=$(echo "$all_ips")
echo "$hosts_str" > "$mosdns_host_file"
echo "$ips_str" > "$mosdns_ips_file"
把这个脚本放在路由器上,然后用 crontab
定期执行就可以了,注意最上面的几个变量要换成你自己的,尤其是 cloudflare_st_url
最好换成自建的1。
mosdns 配置
下面是简化的配置,只保留了跟 Cloudflare 相关的部分。对 Cloudflare 的解析结果启用了 _prefer_ipv4
是因为我发现 IPv4 要比 IPv6 下载速度更快,而且 CloudflareST 对 IPv6 的地址测速太慢了。
log:
level: info
file: "/tmp/mosdns.log"
data_providers:
- tag: geoip
file: "/etc/openclash/GeoIP.dat"
auto_reload: true
- tag: geosite
file: "/etc/openclash/GeoSite.dat"
auto_reload: true
- tag: cloudflare_best_ips
file: "/etc/mosdns/rule/cloudflare_best_ips.txt"
auto_reload: true
- tag: cloudflare_best_hosts
file: "/etc/mosdns/rule/cloudflare_best_hosts.txt"
auto_reload: true
plugins:
- tag: "local_end"
type: forward
args:
upstream:
- addr: 127.0.0.1
timeout: 15
- tag: "remote_end"
type: forward
args:
upstream:
- addr: 127.127.127.127:7874
timeout: 15
- tag: query_is_local_domain
type: query_matcher
args:
domain:
- "lan"
- "local"
- "provider:geosite:cn,apple-cn,category-games@cn"
- tag: query_is_non_local_domain
type: query_matcher
args:
domain:
- "provider:geosite:geolocation-!cn"
- tag: response_has_local_ip
type: response_matcher
args:
ip:
- "provider:geoip:cn"
- "provider:cloudflare_best_ips"
- "192.168.1.0/22"
cname:
- "provider:whitelist"
- tag: response_is_cloudflare
type: response_matcher
args:
ip:
- "provider:geoip:cloudflare"
- tag: cloudflare_best_hosts
type: hosts
args:
hosts:
- "provider:cloudflare_best_hosts"
- tag: cloudflare_best
type: sequence
args:
exec:
- cloudflare_best_hosts
- tag: cloudflare_sequence
type: sequence
args:
exec:
- _prefer_ipv4
- primary:
- cloudflare_best
- if: "(! [_response_valid_answer] )"
exec:
- _drop_response
secondary:
- _return
fast_fallback: 10
- tag: forward_local
type: sequence
args:
exec:
- local_end
- if: response_is_cloudflare
exec:
- cloudflare_sequence
- _return
- tag: "forward_remote"
type: "sequence"
args:
exec:
- remote_end
- tag: "main_sequence"
type: "sequence"
args:
exec:
- if: query_is_local_domain
exec:
- forward_local
- _return
- if: query_is_non_local_domain
exec:
- forward_remote
- _return
- primary:
- forward_local
- if: "(! response_has_local_ip) && [_response_valid_answer]"
exec:
- _drop_response
secondary:
- forward_remote
fast_fallback: 200
servers:
- exec: main_sequence
listeners:
- protocol: udp
addr: ":5335"