Skip to content

mosdns 优选 Cloudflare IP

Posted on:April 15, 2023 at 02:43 PM
0

Cloudflare 的 CDN 应用广泛,包括我自己也有一些服务在用它,使用一个对中国大陆更友好的 IP 来访问 Cloudflare 可以有效提高服务质量,于是就有了优选 Cloudflare IP 的需求。CloudflareST 常用来满足这个需求,结合上篇博客中提到的 mosdns 即可实现在 DNS 层面直接优选 Cloudflare IP,优化内网设备的使用体验。

原理

  1. 从 GeoIP 数据库得到所有的 Cloudflare IP
  2. 通过 CloudflareST 测速,得到最适合我当前网络环境的 Cloudflare IP
  3. mosdns 接管 DNS 请求,并对请求结果在 GeoIP 数据库中查询,判断请求结果是否是 Cloudflare 的 IP
  4. 如果请求结果是 Cloudflare IP,mosdns 通过 hosts 的功能指定返回优选 IP
  5. 针对上篇博客中返回 IP 不是 GeoIP:CN 时会返回 Clash Fake IP 的问题,把优选 IP 加入到 response_has_local_ip 的判断条件中,确保直连 Cloudflare 优选 IP

自动运行脚本

好在 mosdns v4 的 hostsresponse 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"

Footnotes

  1. CloudflareST 自建测速 URL