chinadns + xray + iptables 完美解决软路由 openwrt 分流问题

之前通过 ssrp 插件来解决代理上网的问题,但是总是有一些个别网站无法打开,需要临时设置浏览器代理解决。然而一些比较新的方案如 passwall、openclash 可能能够结局问题但是由于我的软路由比较旧,依赖较多无法安装,刷机怕炸机断网,索性不折腾,通过 iptables 自己创建规则设置代理并分流,且自由度能够更高。

首先介绍我想最终达到的目的,以便判断能否和你需求匹配。再去简单介绍下所用的程序。

大陆 IP/网站直连 + 海外域名/IP 全部走代理 + 部分海外白名单域名直连

  • 大陆网站/IP 直连:无论是通过域名解析的大陆 IP,还是某些应用程序硬编码的 IP 或者其他方式下发的大陆 IP 进行直连。

  • 海外域名/IP 全部走代理:无论在不在 gfwlist 的域名,包括任何海外 IP 走代理。

  • 部分海外白名单域名直连:包括 pt tracker 域名、需要直连的服务器域名,即使 IP 在海外也能够直连。(和上条冲突,例外)

这里就不一步步写我的测试和部署逻辑了,直接附上最终可用的成果,过程中遇到的问题将在最后问题列表追加解析。

chinadns

我的 dns 解析程序选择使用 chinadns 来处理国内域名和海外域名,通过设置两个不同的 DNS 解析服务器,分别解析来避免得到污染过的 IP 地址。

国内域名可以以最快的速度从 dnspod 拿到有效解析,海外域名也能够从 1.1.1.1 拿到正确没有污染的 IP 地址。

选用它,是因为配置文件比 smartdns 简单,单文件体积也小很多。

只是这个程序作者写的文档不是很明白,处于初次接触的人很难快速搞明白解析逻辑和配置文件的逻辑,后面我将以简单的方式告知你。

下载,可根据你的实际情况选择对应的二进制文件

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

wget https://github.com/zfl9/chinadns-ng/releases/download/2024.12.22/chinadns-ng+wolfssl@x86_64-linux-musl@x86_64_v2@fast+lto
mv chinadns-ng+wolfssl@x86_64-linux-musl@x86_64_v2@fast+lto /usr/bin/chinadns
chmod +x /usr/bin/chinadns

/etc/chinadns/config.conf

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

# 国内上游、可信上游
china-dns 119.29.29.29
trust-dns tcp://1.1.1.1

# 域名列表,用于分流
chnlist-file /etc/chinadns/chnlist.txt
gfwlist-file /etc/chinadns/gfwlist.txt
# chnlist-first

group white
group-dnl /etc/chinadns/white.txt
group-upstream 1.0.0.1
group-ipset whiteip

# 收集 tag:chn、tag:gfw 域名的 IP
add-tagchn-ip chnip,chnip6
add-taggfw-ip gfwip,gfwip6

# 用于测试 tag:none 域名的 IP (国内上游)
ipset-name4 chnroute
ipset-name6 chnroute6

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

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

no-ipv6 tag:none@ip:non_china
no-ipv6 tag:gfw

white.txt

tracker.m-team.cc
daydream.dmhy.best

配置解析

china-dns 119.29.29.29 配置大陆域名解析上游、trust-dns tcp://1.1.1.1 配置 gfwlist 域名和其他域名解析上游

大陆域名、gfw 域名来源是什么?

答: chnlist-file /etc/chinadns/chnlist.txt 来源于这个配置文件,同样 gfwlist.txt 是 gfw 域名来源。

海外域名、其他域名来源是什么?

答:不在大陆域名列表的网站、不在 gfwlist 的域名有非常之多,所以当域名都不在上述列表时,将通过两台服务器分别查询。

  • 如果 IP 在大陆,则 DNS 回答响应大陆 IP。

  • 如果 IP 在海外,则 DNS 回答响应 海外 IP。

  • 如果两台服务器解析都不一样,则以海外输出的 IP 为准,以避免大陆 DNS 污染的问题,这也就是配置项为什么是 trust-dns(信任的 DNS 上游) 。

收集 IP 功能是什么?

比如配置中的 add-tagchn-ip chnip,chnip6,是将大陆域名的解析结果存入 ipset 中,如果是 IPV4 则存进 chnip,如果是 IPV6 则进 chnip6.

add-taggfw-ip 同理。

需要注意:add-tagchn-ip 和 add-taggfw-ip 这两个参数项是固定用法,不是可变的,比如 add-tagwhite-ip 这个是错误的。

将 IP 收集起来后,你就可以使用 iptables 进行一些代理或者放行操作。

ipset-name 是什么?

ipset-name4 chnroute 和 ipset-name6 chnroute6 所配置的值都是一个 ipset 集合名字。

它预设了一些大陆的 IP 段,程序将域名解析成 IP 后,要通过与这个 chnroute 集合匹配,如果匹配上了则代表这是大陆的 IP,如果没有匹配上则它就是海外 IP,来决定是否将这个 IP 写入 chnip ,进而去分流。

tag:是什么?

比如 tag 是程序内置的一种标签集合表达方式

固定的有:

  • tag:gfw 来源于 gfwlist-file 配置的域名列表

  • tag:chn 来源于 chnlist-file 配置的域名列表。

  • 还有 tag:none 是即不属于任意 tag 的域名列表。

可变的有:

  • tag:white 比如上文配置中 white 就是由 group {name} 去配置的名字,才能在后面配置中引用。属于自定义组/tag 配置。

no-ipv6 如何配置?

这里配置的是想要忽略 ipv6 解析的规则列表。

大陆域名希望直连,哪怕 IPV6 速度也不慢,也为了能够远程连接穿透等需求这里希望能够正确返回 IPV6。所以这里不能配置 no-ipv6。

海外域名希望屏蔽 IPV6,因为海外 IPV6 路由很差劲,二者服务器也不一定能够支持 IPV6,所以在这里全部屏蔽。

白名单域名希望能够直连,因为这里是 pt tracker 希望服务端能够正确识别到本机的 IPV4 和 IPV6 而不是代理服务器,但这里实际 IP 又是海外的,所以有一些冲突。

最终的配置解答

  • no-ipv6 tag:gfw 忽略 gfw 域名中所有 IPV6 解析。

  • no-ipv6 tag:none@ip:non_china 忽略 none 域名列表中且 IP 解析是非大陆的 IP。

tag:none 是一个特殊的组/tag,它是不存在于任何预设组的列表,即不存在于(chnlist, gfwlist, white.txt)的组。

这里的逻辑是反向的,理解起来有点别扭。

原本需求和配置的关系变成了:

海外所有 IP 忽略 IPV6 (但 white.txt 例外) = gfwlist + none (海外 IP)

到这一步,先忽略后面的代理和路由。域名匹配都能够完成正确解析。

举例说明:

  • 查询 taobao.com 响应大陆 IP 和 大陆 IPV6,通过 dnspod 解析,后续也会直连。

  • 查询 google.com 响应海外 IP,没有 IPV6,通过 1.1.1.1 解析,因为在 gfwlist 中,被 no-ipv6 忽略。

  • 查询 tracker.m-team.cc 响应海外 IP 和海外 IPV6,通过 1.1.1.1 解析,后续也会直连,因为在 white.txt 配置了,没有经过 no-ipv6 处理。

启动服务

/etc/init.d/chinadns

#!/bin/sh /etc/rc.common
START=01
STOP=90
USE_PROCD=1
PROG=/usr/bin/chinadns
start_service(){
         procd_open_instance [chinadns]
         procd_set_param command /usr/bin/chinadns # service executable that has to run in **foreground**.
         procd_append_param command --config /etc/chinadns/config.conf # append command parameters

         procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}

         procd_set_param limits core="unlimited"  # If you need to set ulimit for your process
         procd_set_param file /etc/chinadns/config.conf # /etc/init.d/your_service reload will restart the daemon if these files have changed
         procd_set_param stdout 1 # forward stdout of the command to logd
         procd_set_param stderr 1 # same for stderr
         procd_set_param user root # run service as user nobody
         #procd_set_param user nobody # run service as user nobody
         procd_set_param pidfile /var/run/chinadns.pid # write a pid file on instance start and remove it  on stop
         procd_set_param term_timeout 60 # wait before sending SIGKILL
         procd_close_instance
}

启动和开机自启

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

xray

很强的多合一代理程序,不必多介绍。这里主要是讲解客户端的使用方式,服务器你可以使用任意常见稳定的代理程序,两边都是 xray 也可以。

我的分流规则也都是参考 xray 来的,客户端粘性很高。

下载 xray

mkdir /etc/chinadns/xray
cd /etc/chinadns/xray && wget https://github.com/XTLS/Xray-core/releases/download/v24.12.18/Xray-linux-64.zip
unzip Xray-linux-64.zip

/etc/chinadns/xray/config.json

{
    "log": {
        "loglevel": "warning",
        "access": "none",
        "error": "error.log"
    },
    "routing": {
        "domainStrategy": "AsIs",
        "rules": [{
                "type": "field",
                "inboundTag": ["all-in"],
                "port": 53,
                "outboundTag": "dns-out"
            }, {
                "type": "field",
                "ip": [
                    "geoip:private"
                ],
                "outboundTag": "block"
            }, {

                "type": "field",
                "domain": [
                    "geosite:category-ads-all"
                ],
                "outboundTag": "block"
            }

        ]
    },
    "inbounds": [{
            "port": 12345,
            "protocol": "dokodemo-door",
            "settings": {
                "network": "tcp,udp",
                "followRedirect": true
            },
            "streamSettings": {
                "sockopt": {
                    "tproxy": "tproxy"
                }
            }
        }, {
            "tag": "http2",
            "port": 5556,
            "listen": "0.0.0.0",
            "protocol": "http",
            "sniffing": {
                "enabled": true,
                "destOverride": [
                    "http",
                    "tls"
                ]
            },
            "settings": {
                "auth": "noauth",
                "udp": true,
                "allowTransparent": false
            }
        }, {
            "tag": "socks",
            "port": 5555,
            "listen": "0.0.0.0",
            "protocol": "socks",
            "sniffing": {
                "enabled": true,
                "destOverride": [
                    "http",
                    "tls"
                ]
            },
            "settings": {
                "auth": "noauth",
                "udp": true,
                "allowTransparent": false
            }
        }
    ],
    "outbounds": [{
            "tag": "proxy",
            "protocol": "vmess",
            "settings": {
                "vnext": [{
                        "address": "x.x.x.x",
                        "port": 80,
                        "users": [{
                                "id": "*************************",
                                "alterId": 0,
                                "email": "[email protected]",
                                "security": "auto"
                            }
                        ]
                    }
                ]
            },
            "streamSettings": {
                "sockopt": {
                    "mark": 2
                },
                "network": "tcp"
            },
            "mux": {
                "enabled": true,
                "concurrency": 8
            }
        }, {
            "protocol": "freedom",
            "tag": "direct",
            "streamSettings": {
                "sockopt": {
                    "mark": 2
                }
            }
        }, {
            "protocol": "blackhole",
            "tag": "block"
        }, {
            "tag": "dns-out",
            "protocol": "dns",
            "settings": {
                "address": "1.0.0.1"
            },
            "proxySettings": {
                "tag": "proxy"
            },
            "streamSettings": {
                "sockopt": {
                    "mark": 2
                }
            }
        }
    ]
}

这里的核心配置是

  • 在 rules 增加捕获所有 53 端口的流量,为它设置 "dns-out" 的出口。并在 outbounds 出口新增 dns 协议并配置 sockopt.mark=2

  • 在 outbounds 主要出口以及任意你可能存在的多个出口,均配置 sockopt.mark=2

配置 mark = 2 的目的就是避免流量回环,无限重置,后面将在 iptables 放行 mark = 2 的流量。

启动脚本

/etc/chinadns/xray/xray -config /etc/chinadns/xray/config.json

iptables

由于我的 openwrt 比较旧,无法安装大量依赖以供新的 luci 插件使用,由于 bios 限制,也无法刷机最新的 openwrt,刷 bios 也怕炸机。还是尽量避免折腾这种主网络,同时由于我只熟悉 iptables 所以不去选择 nftables,逻辑应该相同。。

当你掌握了这种路由配置方式后,你可以在任何 openwrt 路由器中折腾代理程序和分流,实用性更强。

proxy.sh

#!/bin/bash
ip route add local default dev lo table 100
ip rule add fwmark 1 table 100 

#代理局域网
iptables -t mangle -N XRAY
iptables -t mangle -A XRAY -d 119.29.29.29/32 -j RETURN
iptables -t mangle -A XRAY -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY -d 100.64.0.0/10 -j RETURN
iptables -t mangle -A XRAY -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A XRAY -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A XRAY -d 192.0.0.0/24 -j RETURN
iptables -t mangle -A XRAY -d 192.168.1.0/24 -j RETURN
iptables -t mangle -A XRAY -d 192.168.0.0/24 -j RETURN
iptables -t mangle -A XRAY -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY -d 240.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A XRAY -m set --match-set whiteip dst -j RETURN
iptables -t mangle -A XRAY -m set --match-set chnroute dst -j RETURN
iptables -t mangle -A XRAY -p tcp --dport 10001:65535 -j RETURN
iptables -t mangle -A XRAY -p udp --dport 10001:65535 -j RETURN
iptables -t mangle -A XRAY -m set --match-set chnip dst -j RETURN
iptables -t mangle -A XRAY -d 192.168.0.0/16 -p tcp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY -m mark --mark 2 -j RETURN
iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A PREROUTING -j XRAY


#代理本机

iptables -t mangle -N XRAY_SELF
iptables -t mangle -A XRAY_SELF -d 119.29.29.29/32 -j RETURN
iptables -t mangle -A XRAY_SELF -d 192.168.1.0/24 -j RETURN
iptables -t mangle -A XRAY_SELF -d 192.168.0.0/24 -j RETURN
iptables -t mangle -A XRAY_SELF -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY_SELF -d 100.64.0.0/10 -j RETURN
iptables -t mangle -A XRAY_SELF -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY_SELF -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A XRAY_SELF -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A XRAY_SELF -d 192.0.0.0/24 -j RETURN
iptables -t mangle -A XRAY_SELF -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY_SELF -d 240.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY_SELF -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A XRAY_SELF -m set --match-set whiteip dst -j RETURN
iptables -t mangle -A XRAY_SELF -m set --match-set chnroute dst -j RETURN
iptables -t mangle -A XRAY_SELF -p tcp --dport 10001:65535 -j RETURN
iptables -t mangle -A XRAY_SELF -p udp --dport 10001:65535 -j RETURN
iptables -t mangle -A XRAY_SELF -m set --match-set chnip dst -j RETURN
iptables -t mangle -I XRAY_SELF -d 192.168.0.0/16 -p tcp ! --dport 53 -j RETURN
iptables -t mangle -I XRAY_SELF -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY_SELF -m mark --mark 2 -j RETURN
iptables -t mangle -A XRAY_SELF -p tcp -j MARK --set-mark 1
iptables -t mangle -A XRAY_SELF -p udp -j MARK --set-mark 1

iptables -t mangle -A OUTPUT -j XRAY_SELF

disable_proxy.sh

#!/bin/bash
iptables -t mangle -D PREROUTING -j XRAY
iptables -t mangle -F XRAY
iptables -t mangle -X XRAY

iptables -t mangle -D OUTPUT -p tcp -j XRAY_SELF
iptables -t mangle -D OUTPUT -p udp -j XRAY_SELF

iptables -t mangle -F XRAY_SELF
iptables -t mangle -X XRAY_SELF

ip rule del fwmark 1 table 100
ip route flush table 100

iptables 命令简单解释

iptables [-t 表名] 管理选项 [链名] [匹配条件] [-j 控制类型]

语法规则

  • 表名链名:指定iptables命令所操作的,未指定表名时将默认使用filter表;

  • 管理选项:表示iptables规则的操作方式,比如:插入增加删除查看等;

  • 匹配条件:指定要处理的数据包的特征,不符合指定条件的数据包不处理;

  • 控制类型:指数据包的处理方式,比如:允许 accept拒绝 reject丢弃 drop日志 LOG等;

主要的配置是参考自 xray 文档的,https://xtls.github.io/document/level-2/tproxy.html#netfilter-%E9%85%8D%E7%BD%AE

结合这张图还有上述配置,这里先介绍下配置和流量路由方向

  • 在 mangle 表里创建了 XRAY 链,用于将 LAN 网段的主机都能够使得都能够走代理。

  • 在 mangle 表创建了 XRAY_SELF 链,用于将 openwrt 本机的网络链接都能够走代理。

  • 为了让 LAN 网段的主机能够走代理的核心命令是 iptables -t mangle -A PREROUTING -j XRAY,在 PREROUTING 里把流量导入 XRAY 链,进行 TPROXY 透明代理转发往 12345 端口。

  • 为了让本机的代理能够走 TPROXY 代理,在 mangle 表的 OUTPUT 链上将流量导入 XRAY_SELF 并标记 mark1。

  • 由于创建了路由表 100,他会将流量导入本地回环,即流量又会重新进入 mangle 表的 PREROUTING 链中。使得后续能够走 tproxy 出去。

  • 其他的配置就是在 XRAY 和 XRAY_SELF 链分别做的 bypass 行为,过滤一些局域网、国内 IP、广播地址、保留地址等

  • 10001:65535 bypass 是为了避免 PT 等很多常用于 10000 以上端口的应用程序走代理。

问题解决

🙋openwrt 路由器本身 dns 解析错误的问题

修改 /etc/resolve.conf, 改掉错误的 192.168.0.1 地址

🙋远程访问慢问题,chnip 中没有解析的远程主机地址,手动添加后正常。

IP 是大陆的但是没有加进去,不知道原因,额外将这个域名增加到 white.txt 重启 chinadns,后续通过 whiteip 绕过。

🙋手机 wifi 显示无网络,或者微信用着用着突然出现无网络链接,或者访问国内网站时很慢,或者 APP 有些图片加载很慢。

  • 以及发现 api.bilibili.com 解析出来是美国等地方的 IP 23.236.97.62

  • blog.csdn.net 也很慢出不来任何数据

则尝试使用 nslookup [blog.csdn.net](<http://blog.csdn.net>) 127.0.0.1:5335nslookup [blog.csdn.net](<http://blog.csdn.net>) 119.29.29.29 均很慢或者超时或者出不来数据

最终定位是因为 dns 全部通过代理解析了,那么需要在规则里绕过。

iptables -t mangle -A XRAY -d 119.29.29.29/32 -j RETURN
iptables -t mangle -A XRAY_SELF -d 119.29.29.29/32 -j RETURN

如果已经缓存了错误的 IP,把大陆网站解析到了海外 CDN,网站还是访问很慢,即解析到了国外,但有没有走代理或者走代理也慢。

可执行下列命令,清空错误的 ipset 列表

ipset flush chnip
ipset flush chnip6

Comments

  • avatar
    tiktok

    博主这个思路好啊, 之前没想到过用ChinaDNS

    2025-01-06 13:19