Shadowsocks 配合 bestroutetb 实现智能分流

 Aug. 13, 2019, 5:23 p.m.   0 comments    shadowsocks bestroutetb linux

之前虽然在软路由上配好了 shadowsocks 的透明代理,本着生命不息折腾不止的心态,一直想尝试一下国内/国际流量的智能分流。

基于目的 IP 地址的智能分流目前有两个流派:

chnroutes 的方案很简单直接,提取所有中国的 IP 地址段,只要不在范围内的一律走国际出口。优点是非常精准,配合 dnsmasq-china-list 解决掉 DNS 污染之后能够非常准确的对国内/国际流量进行分流。缺点也很明显,中国的 IP 地址段数量太庞大,我试着生成了一下,总共产生了8k条以上的路由表项,实在是多得有点可怕。

bestroutetb 项目则提出了一个新的思路,将全球的 IP 地址段分为三类:

  1. 一定走国内出口的 IP 段(中国 IP 段)
  2. 一定走国际出口的 IP 段(默认美国/英国/日本/香港 IP 段,可以自定义)
  3. 可以走国内出口也可以走国际出口的 IP 段

这种方式,通过牺牲一部分分流的精度,实现了对路由表项的压缩,我试着生成了一下,默认配置产生的路由表项只剩下了2k多条,如果可以接受更模糊一点的匹配,可以进一步精简路由表项。

基于这样的对比,很多人都选择使用 bestroutetb 作为智能分流的方案,shadowsocks-libev 的官方文档也推荐了 bestroutetb。然而,这里有一个问题,由于 shadowsocks 透明代理使用防火墙 iptables/nftables 而不是路由表来对目的 IP 地址进行判断实现分流,因此就无法利用防火墙的最长 prefix 匹配规则来对路由表项进行压缩优化。举个例子,以下为 bestroutetb 生成的路由表项:

14.0.0.0/8 net
14.0.0.0/12 vpn
14.0.0.0/21 net

若目的地址为 14.2.0.1,由于 14.0.0.0/12 的 prefix 长度长于 14.0.0.0/8 因而可以获得更高的匹配优先级,路由结果是国际出口。而对于 shadowsocks 透明代理,由于不能使用路由表,在网上搜了一下,发现大多数人的做法都是只保留其中的国内部分的路由规则,因此路由表项就精简成了:

iptables -t nat -A PREROUTING -p tcp -d 14.0.0.0/8 -j RETURN
iptables -t nat -A PREROUTING -p tcp -d 14.0.0.0/21 -j RETURN

即,针对所有国内的 IP 地址段,不走国际出口。拿刚才举例子的 IP 地址 14.2.0.1,由于命中了 14.0.0.0/8 地址段,因而被识别成了国内 IP,这无疑是不正确的。

bestroutetb 的作者也在 github 上说了,bestroutetb 算法的核心是通过网段二分化来优化路由表,这种用法会导致无法对路由表进行有效优化,因此是不可取的。

因而,得出结论:bestroutetb 只适用于路由表,无法用于防火墙规则。

那么还有什么办法可以让 shadowsocks 也用上 bestroutetb?既然 bestroutetb 只能用于路由表,是不是可以想办法让 shadowsocks 用路由表来分流呢?

这里 shadowsocks-libev 自带的透明代理就不能满足需求了,需要用到另一个工具 tun2socks,tun2socks 可以在系统中创建一个虚拟网卡,然后将虚拟网卡的流量导入到 shadowsocks 客户端所提供的 socks proxy中。既然有了虚拟网卡,我们就能很方便的使用路由表来管理流量,从而实现利用 bestroutetb 进行智能分流的目的。

tun2socks 的安装不再赘述,安装完成后,首先创建一个 tun 虚拟网卡,我给命名为 tun1

ip tuntap add dev tun1 mode tun user root

给虚拟网卡分配地址,可以随便分配一个,但是注意不能跟当前局域网的 IP 地址冲突,我给分配了一个 192.168.2.1

ip addr add 192.168.2.1 dev tun1

启用虚拟网卡 tun1

ip link set dev tun1 up

启动 tun2socks, 根据实际情况填写参数,我的 shadowsocks 客户端监听的是 192.168.1.1:1080,因而命令行参数是:

badvpn-tun2socks --tundev tun1 --netif-ipaddr 192.168.2.1 --netif-netmask 255.255.255.255 --socks-server-addr 192.168.1.1:1080

至此,tun2socks 已经可以使用,所有路由到虚拟网卡 tun1 的流量将会被转发至 shadowsocks。

安装 bestroutetb:

npm install -g bestroutetb

使用 bestroutetb 生成添加路由规则的脚本:

bestroutetb -p custom -o add-routes --rule-format=$'ip route add %prefix/%length dev %gw table ss\n'
--gateway.net=ppp0 --gateway.vpn=tun1 --header=$'#!/bin/sh\n\n'

对应的删除路由规则的脚本:

bestroutetb -p custom -o del-routes --rule-format=$'ip route del %prefix/%length dev %gw table ss\n'
--gateway.net=ppp0 --gateway.vpn=tun1 --header=$'#!/bin/sh\n\n'

由于我并不希望让所有设备都使用这张路由表,因此在每一条路由项后面都加了 table ss,同时需要将特定来源的 IP 加入这张路由表的白名单之中从而实现源地址路由,因此需要在添加路由规则的脚本中加入:

ip rule add from 192.168.1.xx table ss

相应的删除路由规则的脚本中也要加入:

ip rule del from 192.168.1.xx table ss

使用 systemd 以实现自动启动,贴一下我的 tun2socks.service 文件:

[Unit]
Description=Tun2Socks Service
Requires=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStartPre=/usr/sbin/ip tuntap add dev tun1 mode tun user root
ExecStartPre=/usr/sbin/ip addr add 192.168.2.1 dev tun1
ExecStartPre=/usr/sbin/ip link set dev tun1 up
ExecStart=/usr/local/bin/badvpn-tun2socks --tundev tun1 --netif-ipaddr 192.168.2.1 --netif-netmask 255.255.255.255 --socks-server-addr 192.168.1.1:1080
ExecStartPost=/usr/local/bin/add-routes
ExecStopPost=/usr/local/bin/del-routes
ExecStopPost=/usr/sbin/ip link del dev tun1
[Install]
WantedBy=multi-user.target

启用 tun2socks.service

systemd enable tun2socks
systemd start tun2socks