我的例子里做到的效果是(本文以分流中国大陆IP/非中国大陆IP为例)
1. 正常状态: ip分流,DNS无污染,中国大陆直连出去,除了少量特殊配置比如icloud啥的强制直连,剩下的走隧道
2. 异常状态: 自动切换到全部直连,DNS有污染
3. 故障恢复: 自动恢复到正常状态
实际上通过多个tun设备分别连接不同的隧道出口+细化的路由表,是可以做到更细粒度的控制的
比如亚太方向流量走HK的隧道,美洲方向流量走LA隧道,欧洲走荷兰隧道等等
如有需要请自行研究,大体框架和下面的是一样的
这个和基于L7或者Address Lists分流方案的主要区别有两点
1. 性能: 通过路由器最擅长的路由来处理,因为直接网段/接口隔离不需要使用mangle来给流量打标,且不使用L7识别,不需要破坏硬件加速转发特性
2. 故障波及范围小: 假设隧道故障,大陆IP不受影响,且未被墙的非大陆IP会在短时间后快速变为直连状态(尽管也可以通过script处理,但是如果你体验过大批量导入/删除Address Lists的卡卡卡卡卡,我猜你是不会这么选的)
前提,你要收一份对应的路由表
方法1. 参考BakaCai的博客《在家也要玩BGP 之 Mikrotik Fan Boy版》自己收全表过滤出来
方法2. 利用nchnroutes项目跑一份出来
方法3. 直接拿别人跑好的,比如我用GitHub action定时跑的这个项目
方法4. 其他现成的地址段配置转换
接下来需要一台支持路由协议(BGP/OSPF等)的主路由,和一台可以运行clash等隧道+bird的隧道端点机(openwrt或者debian等常见linux发行版都可以)
我这边的环境如下
设备及系统版本
- 主路由MikroTik AC^2(ros 6.48.6 lt)
- 隧道端点NUC5(debian 11.3)
网段
- PC/WLAN等需要透明网关的(lan桥): 192.168.1.0/24
- 直通(ether5): 192.168.255.0/24
这边把ether5这个口踢出lan桥,单独给分配192.168.255.1/24,lan桥分配192.168.1.1/24
端点机分配192.168.255.254/24
路由配置打通两个网段
- /ip route
- add distance=1 gateway=pppoe-out routing-mark=bypass
- add distance=1 dst-address=10.0.0.0/8 gateway=lan routing-mark=bypass
- add distance=1 dst-address=169.254.0.0/16 gateway=lan routing-mark=bypass
- add distance=1 dst-address=172.16.0.0/12 gateway=lan routing-mark=bypass
- add distance=1 dst-address=192.168.0.0/16 gateway=lan routing-mark=bypass
- add distance=1 dst-address=224.0.0.0/4 gateway=lan routing-mark=bypass
- add distance=1 dst-address=255.255.255.255/32 gateway=lan routing-mark=bypass
- /ip route rule
- add interface=lan table=main
- add interface=ether5 table=bypass
复制代码
两边互ping一下确认是否ok(windows防火墙可能会拦截icmp)
接下来给lan配一个dns劫持,还有dns自动切换
- /ip firewall nat
- add action=dst-nat chain=dstnat comment="Hijacking for DNS" dst-port=53 protocol=udp src-address=192.168.1.0/24 to-addresses=192.168.1.1 to-ports=53
- /system script
- add dont-require-permissions=yes name=use-default-dns owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/ip dns set allow-remote-requests=yes servers=119.29.29.29,223.5.5.5 \r\n/ip dns cache flush"
- add dont-require-permissions=yes name=use-sgw-dns owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/ip dns set allow-remote-requests=yes servers=192.168.255.254\r\n/ip dns cache flush"
复制代码
注意netwatch要在隧道端点的dns服务启动后再加(比如这个例子里的clash),或者添加后先disable,最后再回来enable,不然就半断网了(解析不了dns)
- /tool netwatch
- add down-script=use-default-dns host=192.168.255.254 interval=5s up-script=use-sgw-dns
复制代码
接下来是路由协议的配置,我之前使用7.x版本的ros+ospf,不是很稳定,时不时有一些小毛病,这里就回退到6.x的LT版本上用bgp来跑,截至目前没有发现不正常的地方
不管是BGP还是OSPF在这个场景上作用都是一样的,就是把端点机上宣过来的路由加到main表里,如果使用OSPF需要在防火墙放行ospf协议和组播(或者使用点对点模式),bgp的话有上面的路由规则就可以了
- /routing bgp instance
- set default router-id=192.168.255.1
- /routing bgp peer
- add multihop=yes name=sgw remote-address=192.168.255.254 remote-as=65531
复制代码
然后是debian的配置
软件选择bird2来跑路由协议,clash premium跑隧道(自带tun和dns,可以省下tun2socks和smartdns)
替换你的clash订阅地址,按需修改external-controller/external-ui
/etc/clash/config.yaml
- dns:
- enable: true
- ipv6: true
- listen: 0.0.0.0:53
- enhanced-mode: redir-host
- use-hosts: true
- default-nameserver:
- - 119.29.29.29
- - 223.5.5.5
- nameserver:
- - 119.29.29.29
- - 223.5.5.5
- fallback:
- - https://dns.google/dns-query
- - https://cloudflare-dns.com/dns-query
- - https://1.1.1.1/dns-query
- - https://8.8.8.8/dns-query
- - https://8.8.4.4/dns-query
- fallback-filter:
- geoip: true
- ipcidr:
- - 240.0.0.0/4
- tun:
- enable: true
- stack: system
- auto-detect-interface: true
- port: 7890
- socks-port: 7891
- redir-port: 7893
- allow-lan: true
- mode: Rule
- log-level: silent
- external-controller: '0.0.0.0:8080'
- proxy-groups:
- - name: PROXY
- type: select
- use:
- - subscribe
- proxy-providers:
- subscribe:
- type: http
- url: 替换成你的clash订阅地址
- interval: 86400
- path: ./proxy/subscribe.yaml
- health-check:
- enable: false
- interval: 600
- # lazy: true
- url: http://www.gstatic.com/generate_204
- rule-providers:
- icloud:
- type: http
- behavior: domain
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/icloud.txt"
- path: ./ruleset/icloud.yaml
- interval: 86400
- apple:
- type: http
- behavior: domain
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/apple.txt"
- path: ./ruleset/apple.yaml
- interval: 86400
- google:
- type: http
- behavior: domain
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/google.txt"
- path: ./ruleset/google.yaml
- interval: 86400
- proxy:
- type: http
- behavior: domain
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/proxy.txt"
- path: ./ruleset/proxy.yaml
- interval: 86400
- direct:
- type: http
- behavior: domain
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/direct.txt"
- path: ./ruleset/direct.yaml
- interval: 86400
- private:
- type: http
- behavior: domain
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/private.txt"
- path: ./ruleset/private.yaml
- interval: 86400
- telegramcidr:
- type: http
- behavior: ipcidr
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/telegramcidr.txt"
- path: ./ruleset/telegramcidr.yaml
- interval: 86400
- cncidr:
- type: http
- behavior: ipcidr
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/cncidr.txt"
- path: ./ruleset/cncidr.yaml
- interval: 86400
- lancidr:
- type: http
- behavior: ipcidr
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/lancidr.txt"
- path: ./ruleset/lancidr.yaml
- interval: 86400
- applications:
- type: http
- behavior: classical
- url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/applications.txt"
- path: ./ruleset/applications.yaml
- interval: 86400
- rules:
- - PROCESS-NAME,clash,DIRECT
- - RULE-SET,applications,DIRECT
- - RULE-SET,private,DIRECT
- - RULE-SET,icloud,DIRECT
- - RULE-SET,apple,DIRECT
- - RULE-SET,google,DIRECT
- - RULE-SET,proxy,PROXY
- - RULE-SET,direct,DIRECT
- - RULE-SET,lancidr,DIRECT
- - RULE-SET,cncidr,DIRECT
- - RULE-SET,telegramcidr,PROXY
- - GEOIP,LAN,DIRECT
- - GEOIP,CN,DIRECT
- - MATCH,PROXY
复制代码
最好用yacd之类接进去确认一下GLOBAL是不是direct
如果有smartdns或者dnsmasq会变得很麻烦,需要解决端口冲突问题改成解析链,再加上对应的PROCESS-NAME DIRECT规则,总之不建议开这些
/etc/bird/bird.conf
- log syslog all;
- router id 192.168.255.254;
- protocol device {
- scan time 60;
- }
- protocol kernel {
- ipv4 {
- import none;
- export all;
- };
- }
- protocol static {
- ipv4;
- include "routes4.conf";
- }
- protocol bgp {
- local as 65531;
- neighbor 192.168.255.1 as 65530;
- source address 192.168.255.254;
- ipv4 {
- import none;
- export all;
- };
- }
复制代码
crontab添加2条定时刷新路由
- 0 2 * * * curl -s https://api.xn--7ovq92diups1e.com/ncr?device=utun -o /etc/bird/routes4.conf
- 0 3 */1 * * /etc/init.d/bird reload
复制代码
手动执行一次初始化
- curl -s https://api.xn--7ovq92diups1e.com/ncr?device=utun -o /etc/bird/routes4.conf
复制代码
就可以启动clash和bird2的服务了(以及使能前面的netwatch)
再加一个健康检查脚本,访问Google异常并且重试失败后关闭bird和icmp响应,触发ros自动切换
/root/check.sh
- #!/usr/bin/bash
- COUNT=0
- MAX_COUNT=3
- while [ $COUNT -lt $MAX_COUNT ]
- do
- SER=0
- NET=0
- if [ $(curl --connect-timeout 5 --interface utun -w "%{http_code}" -s https://www.google.com/generate_204) -eq 204 ];then
- NET=1
- fi
- if /etc/init.d/bird status|grep Active|grep -q running;then
- SER=1
- fi
- if [ $NET -eq 1 ] && [ $SER -eq 0 ];then
- /etc/init.d/bird start
- echo 0 >/proc/sys/net/ipv4/icmp_echo_ignore_all
- exit 0
- fi
- if [ $NET -eq 0 ] && [ $SER -eq 1 ];then
- let COUNT+=1
- if [ $COUNT -eq $MAX_COUNT ];then
- /etc/init.d/bird stop
- echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all
- fi
- continue
- fi
- exit 0
- done
复制代码
crontab添加规则
测试一下
可见访问国外ip和国内ip已经不一样了,分流完成 |