之前在openwrt下一直用chinadns、dnsmasq搭配v2ray实现的透明代理上网,但是chinadns很久没维护了,另外看到了chinadns-ng,除了支持原版chinadns的功能外,还能实现dns over tcp/tls,按域名、ip分流,ipv6,还支持dns缓存,最新版本已经可以全面替代dnsmasq,dns查询性能大幅度提升。所以研究了一下,部署以后通过cdn warp后的代理速度明显提升,访问github网页速度提升了约40-50%。

下面是部署的步骤:

1.下载chinadns-ng

1)从github下载的现成的二进制文件,需注意CPU架构要匹配(之前自己编译openwrt下的chinadns-ng版本未成功。。。联机编译网络环境太差了,另外openwrt下的源码版本有点老,不支持很多新特性,所以还是用原版的靠谱)。把二进制文件拷贝到/usr/bin下。

改名为chinadns-ng,并附加执行权限:

mv chinadns-ng+wolfssl@aarch64-linux-musl@generic+v8a@fast+lto /usr/bin/chinadns-ng
chmod +x /usr/bin/chinadns-ng

运行一下:

/usr/bin/chinadns-ng -V

如果显示正常就ok了

2)从github下载source,提取其中的res文件夹下的文件,拷贝到/etc/chinadns-ng文件夹下,没有的话就创建一个,主要有以下几个文件:

 # ls /etc/chinadns-ng
chnlist.txt               chnroute6.ipset           disable_chnroute.nftset   disable_gfwip6.nftset     gfwlist.txt               update-chnroute-nft.sh    update-chnroute6.sh
chnroute.ipset            chnroute6.nftset          disable_chnroute6.nftset  gfwip.nftset              update-all.sh             update-chnroute.sh        update-gfwlist.sh
chnroute.nftset           direct.txt                disable_gfwip.nftset      gfwip6.nftset             update-chnlist.sh         update-chnroute6-nft.sh

注意: direct.txt, disable_chnroute.nftset, disable_chnroute6.nftset, gfwip.nftset, gfwip6.nftset, disable_gfwip.nftset, disable_gfwip6.nftset 是我创建的,后面细说。

建议启用前通过update*.sh进行更新

a) 其中direct.txt中内容为需要通过国内114解析的域名,主要是v2ray服务端域名!这一点非常重要,v2ray服务端域名一定要由国内dns解析,否则无法连接。例如v2ray服务端域名是xxx.com,则direct.txt内容可以为:

xxx.com
ntp.org
vultur.com

b) disable_chnroute.nftset内容为:

flush set inet global chnroute

c) disable_chnroute6.nftset内容为:

flush set inet global chnroute6

d) gfwip.nftset内容为:

add table inet global
add set inet global gfwip { type ipv4_addr;flags interval; }

e) gfwip6.nftset内容为:

add table inet global
add set inet global gfwip6 { type ipv4_addr;flags interval; }

f) disable_chnroute.nftset内容为:

flush set inet global gfwip

g) disable_chnroute6.nftset内容为:

flush set inet global gfwip6

2.配置chinadns-ng

1)创建并修改配置文件/etc/config/chinadns-ng:

# 监听地址和端口
bind-addr 0.0.0.0
bind-port 5353

# 国内 DNS
china-dns 114.114.114.114
china-dns 223.5.5.5
china-dns 119.29.29.29
china-dns tls://223.6.6.6
china-dns tls://1.12.12.12
china-dns tls://120.53.53.53


# 国外 DNS
trust-dns 127.0.0.1#5354
trust-dns tcp://127.0.0.1#5356
trust-dns 127.0.0.1#5358
trust-dns 127.0.0.1#5360

# 列表文件
chnlist-file /etc/chinadns-ng/chnlist.txt
gfwlist-file /etc/chinadns-ng/gfwlist.txt

# group文件
group direct
group-dnl /etc/chinadns-ng/direct.txt
group-upstream tls://223.5.5.5

# 收集 tag:chn、tag:gfw 域名的 IP (可选)
# 相关 family,table,set名称要与nftset文件中的一致,否则无法生效
# 我这里只动态收集gfwlist(tag:gfw)里网址解析出来的gfwip和gfwip6,应用于v2ray的gfwlist模式中
#add-tagchn-ip inet@global@chnip,inet@global@chnip6
add-taggfw-ip inet@global@gfwip,inet@global@gfwip6

# 测试 tag:none 域名的 IP (针对国内上游)
# 相关 family,table,set名称要与nftset文件中的一致,否则无法生效
ipset-name4 inet@global@chnroute
ipset-name6 inet@global@chnroute6

# dns 缓存
cache 4096
cache-stale 86400
cache-refresh 20

# verdict 缓存 (用于 tag:none 域名)
verdict-cache 4096

2)注册为系统服务

创建并编辑/etc/init.d/chinadns-ng

#!/bin/sh /etc/rc.common
# init script for chinadns-ng

START=85
STOP=10

USE_PROCD=1
PROG=/usr/bin/chinadns-ng
CONF=/etc/config/chinadns-ng

#下列NFTSET文件中相关 family,table,set名称要也与chinadnsng配置文件中的一致,否则无法生效
enable_nft_rules (){
    nft -f /etc/chinadns-ng/chnroute.nftset
    nft -f /etc/chinadns-ng/chnroute6.nftset
    nft -f /etc/chinadns-ng/gfwip.nftset
    nft -f /etc/chinadns-ng/gfwip6.nftset
}

disable_nft_rules (){
    nft -f /etc/chinadns-ng/disable_chnroute.nftset
    nft -f /etc/chinadns-ng/disable_chnroute6.nftset
    nft -f /etc/chinadns-ng/disable_gfwip.nftset
    nft -f /etc/chinadns-ng/disable_gfwip6.nftset
}

start_service() {
    [ -x "$PROG" ] || exit 1
    [ -f "$CONF" ] || exit 1
    echo "[+] 启动 chinadns-ng 服务"
    procd_open_instance
    procd_set_param command $PROG -C $CONF
    procd_set_param respawn
    enable_nft_rules
    # 检查 v2ray 是否在运行,如果在运行,需要重启
    if  pgrep -f v2ray >/dev/null 2>&1; then
        echo "[+] v2ray 正在运行, 需要重启..."
        /etc/init.d/v2ray restart
        sleep 5   # 等几秒确保 v2ray 完全启动
    fi
    procd_close_instance
}

stop_service()  {
    echo "[+] 停止 chinadns-ng 服务"
    disable_nft_rules
}

然后执行:

chmod +x /etc/init.d/chinadns-ng
/etc/init.d/chinadns-ng enable
/etc/init.d/chinadns-ng start

3.配置v2ray

1)修改配置文件(/etc/config/v2ray.json)

{
  "inbounds": [
    {    
      "protocol": "dokodemo-door",
      "port": 1060,
      "listen":"0.0.0.0",
      "sniffing": {
        "enabled": false,
        "destOverride": ["http", "tls"]
      },
       "settings": {
         "network": "tcp,udp",
         "followRedirect": true 
       }
    },
    {
      "protocol": "dokodemo-door",
      "listen":"0.0.0.0",
      "port": 5354,
      "settings": {
        "address": "8.8.8.8",
        "port": 53,
        "network": "udp",
        "timeout": 0,
        "followRedirect": false
      }
    },
    {
      "protocol": "dokodemo-door",
      "listen":"0.0.0.0",
      "port": 5356,
      "settings": {
        "address": "8.8.8.8",
        "port": 53,
        "network": "tcp",
        "timeout": 0,
        "followRedirect": false
      }
    },
    {
      "protocol": "dokodemo-door",
      "listen":"0.0.0.0",
      "port": 5358,
      "settings": {
        "address": "1.1.1.1",
        "port": 53,
        "network": "udp",
        "timeout": 0,
        "followRedirect": false
      }
    },
    {
      "protocol": "dokodemo-door",
      "listen":"0.0.0.0",
      "port": 5360,
      "settings": {
        "address": "8.8.4.4",
        "port": 53,
        "network": "udp",
        "timeout": 0,
        "followRedirect": false
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "tag": "proxy",
      "settings": {
        "vnext": [
          {
            "address": "xxx.com",
            "port": 443,
            "users": [
              {
                "id": "***************uuid************************",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "ws",
        "security": "tls",
        "wsSettings": {
          "path": "/mypath"
        },
        "tcpSettings": {
          "allowInsecureCiphers": false
        }
      }
    }
  ]
}

2)调整启动脚本(/etc/init.d/v2ray)

#!/bin/sh /etc/rc.common
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#
# To use this file, install chinadns-ng,v2ray,knot-dig first
#

START=90
USE_PROCD=1

DOMAIN="xxx.com"
DEFAULT_DNS_SERVER="223.6.6.6" #需要是支持dot的dns服务器
DOMESTIC_DNS_SERVERS="119.29.29.29 223.5.5.5 180.76.76.76"
LOCAL_IP="127.0.0.1"
CHINADNSNG_PORT="5353"
CHINADNSNG_FILES_PATH="/etc/chinadns-ng/"
# 本配置文件中NFT默认参数为:
# family:inet
# table:global
# set:chnroute,chnroute6,gfwip,gfwip6
# 上述值需与chinadnsng配置文件中的一致,否则无法生效:
# add-taggfw-ip inet@global@gfwip,inet@global@gfwip6
# ipset-name4 inet@global@chnroute
# ipset-name6 inet@global@chnroute6
# 且下列NFTSET文件中相关 family,table,set名称要也与chinadnsng配置文件中的一致,否则无法生效
CHNROUTE_NFT_NAME="chnroute.nftset"
CHNROUTE6_NFT_NAME="chnroute6.nftset"
GFWIP_NFT_NAME="gfwip.nftset"
GFWIP6_NFT_NAME="gfwip6.nftset"

V2RAY_PORT="1060"
V2RAY_DNS_PORTS="5354 5356 5358 5360" #5356走tcp
V2RAY_BIN="/usr/bin/v2ray"
V2RAY_CONF="/etc/config/v2ray.json"

set_multi_domestic_dns() {
    current_dns_servers_list=`uci get dhcp.@dnsmasq[0].server 2>/dev/null`
    min_len=$(( ${#DEFAULT_DNS_SERVER} < ${#current_dns_servers_list} ? ${#DEFAULT_DNS_SERVER} : ${#current_dns_servers_list} ))
    if [ x${DEFAULT_DNS_SERVER:0:$min_len} != x${current_dns_servers_list:0:$min_len} ]; then
        echo "[+] 设置使用国内DNS服务器"
        uci -q delete dhcp.@dnsmasq[0].server
        uci add_list dhcp.@dnsmasq[0].server=${DEFAULT_DNS_SERVER}
        for dns_server in $DOMESTIC_DNS_SERVERS; do
            uci add_list dhcp.@dnsmasq[0].server="${dns_server}"
        done
        uci set dhcp.@dnsmasq[0].noresolv=0
        uci set dhcp.@dnsmasq[0].nohosts=0
        uci commit dhcp
        echo "[+] 重启dnsmasq服务"
        /etc/init.d/dnsmasq restart 1>/dev/null 2>&1
    fi
}

set_multi_foreign_dns() {
    current_dns_servers_list=`uci get dhcp.@dnsmasq[0].server 2>/dev/null`
    min_len=$(( ${#LOCAL_IP} < ${#current_dns_servers_list} ? ${#LOCAL_IP} : ${#current_dns_servers_list} ))
    if [ x${LOCAL_IP:0:$min_len} != x${current_dns_servers_list:0:$min_len} ]; then
        echo "[+] 设置使用ChinaDNSNG DNS服务器"
        chinadnsng_addr_port=${LOCAL_IP}"#"${CHINADNSNG_PORT}
        uci -q delete dhcp.@dnsmasq[0].server
        uci add_list dhcp.@dnsmasq[0].server=${chinadnsng_addr_port}
        uci set dhcp.@dnsmasq[0].noresolv=1
        uci set dhcp.@dnsmasq[0].nohosts=1
        uci commit dhcp
        echo "[+] 重启dnsmasq服务"
        /etc/init.d/dnsmasq restart 1>/dev/null 2>&1
    fi
}

append_chnroute_list() {
    # 检查和创建 set:chnroute
    if ! nft list set inet global chnroute 2>/dev/null >/dev/null; then
        echo "[*] 创建 inet global 表和 chnroute 集合"
        nft -f ${CHINADNSNG_FILES_PATH}${CHNROUTE_NFT_NAME} 
    fi   
    # 检查和创建 set:chnroute6
    if ! nft list set inet global chnroute6 2>/dev/null >/dev/null; then
        echo "[*] 创建 inet global 表和 chnroute6 集合"
        nft -f ${CHINADNSNG_FILES_PATH}${CHNROUTE6_NFT_NAME} 
    fi   

    # 添加私有地址段
    echo "[+] 添加内网地址段到 chnroute"
    nft add element inet global chnroute  { \
        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 \
    }

    # 加载 VPS 域名解析结果
    echo "[+] 解析 $DOMAIN 并加入 chnroute/chnroute6"
    # 处理 IPv4
    dig +short A "$DOMAIN" @"${DEFAULT_DNS_SERVER}" \
    |   grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' \
    |   sort -u \
    |   while read -r ip; do
            if [ "$ip" != "$DEFAULT_DNS_SERVER" ] && [ "$ip" != "0.0.0.0" ]; then
                nft add element inet global chnroute { $ip } 2>/dev/null
                echo "已经加入 IPv4: $ip"
            else
                echo "忽略无效 IPv4: $ip"
            fi
        done

    # 处理 IPv6
    dig +short AAAA "$DOMAIN" @"${DEFAULT_DNS_SERVER}" \
        | grep -Eo '([0-9a-fA-F:]{2,})' \
        | sort -u \
        | while read -r ip6; do
            if [ -n "$ip6" ] && [ "$ip6" != "::" ]; then
                nft add element inet global chnroute6 { $ip6 } 2>/dev/null
                echo "已经加入 IPv6: $ip6"
            else
                echo "忽略无效 IPv6: $ip6"
            fi
        done

    echo "    chnroute 已载入 $(nft list set inet global chnroute | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' | wc -l) 条目"

}

append_gfwip_list(){
    # 检查和创建 set:gfwip
    if ! nft list set inet global gfwip 2>/dev/null >/dev/null; then
        echo "[*] 创建 inet global 表和 gfwip 集合"
        nft -f ${CHINADNSNG_FILES_PATH}${GFWIP_NFT_NAME} 
    fi
    # 检查和创建 set:gfwip6
    if ! nft list set inet global gfwip6 2>/dev/null >/dev/null; then
        echo "[*] 创建 inet global 表和 gfwip6 集合"
        nft -f ${CHINADNSNG_FILES_PATH}${GFWIP6_NFT_NAME} 
    fi
}

create_empty_chain(){
    # 确保 table 存在
    nft create table inet global 2>/dev/null

    # 确保 prerouting/output 链存在
    nft create chain inet global prerouting { type nat hook prerouting priority dstnat\; } 2>/dev/null
    nft create chain inet global output { type nat hook output priority -100\; } 2>/dev/null

    # 清空链
    nft flush chain inet global prerouting
    nft flush chain inet global output
}

create_chain_rules(){
    create_empty_chain
    nft add rule inet global prerouting ip daddr ${LOCAL_IP} tcp dport \{${CHINADNSNG_PORT},${V2RAY_DNS_PORTS// /,}\} return 2>/dev/null
    nft add rule inet global prerouting ip daddr ${LOCAL_IP} udp dport \{${CHINADNSNG_PORT},${V2RAY_DNS_PORTS// /,}\} return 2>/dev/null
    nft add rule inet global output ip daddr ${LOCAL_IP} tcp dport \{${CHINADNSNG_PORT},${V2RAY_DNS_PORTS// /,}\} return 2>/dev/null
    nft add rule inet global output ip daddr ${LOCAL_IP} udp dport \{${CHINADNSNG_PORT},${V2RAY_DNS_PORTS// /,}\} return 2>/dev/null
}

enable_chnroute_firewall_rules(){
    create_chain_rules
    nft add rule inet global prerouting ip daddr != @chnroute tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
    nft add rule inet global output ip daddr != @chnroute tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
    nft add rule inet global prerouting ip6 daddr != @chnroute6 tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
    nft add rule inet global output ip6 daddr != @chnroute6 tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
}

enable_gfwip_firewall_rules(){
    create_chain_rules
    nft add rule inet global prerouting ip daddr @gfwip tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
    nft add rule inet global output ip daddr @gfwip tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
    nft add rule inet global prerouting ip6 daddr @gfwip6 tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
    nft add rule inet global output ip6 daddr @gfwip6 tcp dport 0-65535 counter redirect to :${V2RAY_PORT} 2>/dev/null
}

disable_v2ray_rules() {
    create_empty_chain
    set_multi_domestic_dns
    echo "ingfw" > /tmp/v2raymode.txt   
}

stop_service()  {
    echo "[+] 停止 v2ray 服务"
    disable_v2ray_rules
}

enable_v2ray_rules(){
    running_v2ray_mode=$(cat /tmp/v2raymode.txt 2>/dev/null | tr -d '\r')
    v2ray_mode=`uci get advancedconfig.@rules[0].v2raymode 2>/dev/null`

    if [ x${v2ray_mode} = x${running_v2ray_mode} ]; then
        echo "[+] v2ray模式未变化"
    else
        disable_v2ray_rules
        if [ "${v2ray_mode}" = "outlands" ]; then
            echo "[+] 设置${v2ray_mode}(境外全局代理模式)模式中"
            append_chnroute_list
            enable_chnroute_firewall_rules
            set_multi_foreign_dns
        
        elif [ "${v2ray_mode}" = "gfwlist" ]; then
            echo "[+] 设置${v2ray_mode}(白名单代理模式)模式中"
            append_gfwip_list
            enable_gfwip_firewall_rules
            set_multi_foreign_dns
        
        elif [ "${v2ray_mode}" = "ingfw" ]; then
            echo "[+] 启动墙内访问模式"
        fi
        echo "${v2ray_mode}" > /tmp/v2raymode.txt
    fi
}

start_service()  {
    # 检查 chinadns-ng 是否在运行
    if ! pgrep -f chinadns-ng >/dev/null 2>&1; then
        echo "[+] chinadns-ng 未运行, 启动中..."
        /etc/init.d/chinadns-ng start
        sleep 2   # 等几秒确保 chinadns-ng 完全启动
    fi
    echo "[+] 启动 v2ray 服务"
    mkdir -p /var/log/v2ray
    ulimit -n 65535
    procd_open_instance
    procd_set_param command $V2RAY_BIN run -config $V2RAY_CONF
    procd_set_param file $V2RAY_CONF
    procd_set_param respawn
    procd_set_param stdout 1
    procd_set_param stderr 1
    procd_set_param pidfile /var/run/v2ray.pid
    enable_v2ray_rules
    procd_close_instance
}

service_triggers() {
    procd_add_reload_trigger "advancedconfig"
}

4.调整dnsmasq,chinadns等配置,避免冲突。

echo '' > /etc/dnsmasq.conf
/etc/init.d/chinadns disable
/etc/init.d/chinadns stop
rm /root/start_multi_chinadns.sh

然后在luci界面中,把“网络->dhcp/dns->转发”菜单下的dns转发改成“127.0.0.1#5353”,保存后重启openwrt。

参考:

1.https://github.com/zfl9/chinadns-ng

2.chatgpt