Nova Kwok's Awesome Blog

使用 VPN + 静态路由构建一个稍好一些的家庭网络方案

这是一篇关于网络改造的记录,文章很简单,搞起来很开心。

在 Tuki 的文章「利用树莓派做旁路网关实现家居全局透明科学上网实践」中,我们知道,可以通过 SS-Redir + Unbound + DNScrypt 的方式实现国内外分流并设置海外地址自动走代理,但是这个方案有一些弊端,比如:

  1. 出口 IP 单一(只有 SS Server 的出口 IP),全家常年共享一个海外出口 IP,容易被反向关联 IP -> 用户
  2. 大量的 iptables + ip_set 不好调试
  3. 容易干扰到已有的服务(比如 iptables 可能会干扰到 Wireguard)
  4. 由于使用 iptables, traceroute 等功能完全不可用

为了解决以上几个问题,有如下解决方案:

  1. 放弃单一 IP 出口,使用动态的出口方案(比如商业化的 VPN,别人的 VPS,朋友家的家宽,或者 VPNGate 等类似方案)
  2. 不使用 ss-redir + iptables,而使用 VPN + 静态路由分流
  3. 不使用 Unbound + DNSCrypt,直接使用 Unbound 回源走 VPN 的上游 DNS 服务器,当然,Unbound 还是需要区分国内域名不回源

大概结构如下图:

Make OpenVPN file

这里为了演示方便,我使用 OpenVPN 作为例子.

如果你在一个 OpenVPN 没法拨通的地区…(那你应该先自己解决一下这个问题再继续往后看)

在我们拿到一个 OpenVPN 文件后,由于我们需要在内网内共享这个隧道,所以首先需要做的是排除内网地址段,不然 VPN 通了一瞬间你的 SSH 也掉了就很尴尬,很简单,在文件中加入以下几行:

route 127.0.0.1 255.255.255.255 net_gateway
route 192.168.0.0 255.255.0.0 net_gateway
route 172.16.0.0 255.255.0.0 net_gateway

此外,为了防止连接你自己的某些(比如用来帮你连接 OpenVPN 的)服务打环,还需要排除对应的 IP

route 22.33.44.55 255.255.255.255 net_gateway

现在内网 IP 和某些特殊的 IP 不会被走到 VPN 的隧道上了,下一步需要让国内 IP 也不走这个隧道(而是使用默认网关出去),这里使用 https://github.com/fivesheep/chnroutes 来获得国内 IP 段:

wget https://raw.githubusercontent.com/fivesheep/chnroutes/master/chnroutes.py
python2 chnroutes.py

这个脚本会通过 APNIC 上中国 IP 段生成一个 routes.txt,格式类似:

route 1.0.1.0 255.255.255.0 net_gateway 5
route 1.0.2.0 255.255.254.0 net_gateway 5
route 1.0.8.0 255.255.248.0 net_gateway 5
route 1.0.32.0 255.255.224.0 net_gateway 5
route 1.1.0.0 255.255.255.0 net_gateway 5
route 1.1.2.0 255.255.254.0 net_gateway 5

2023-03-30 更新:这里请不要按照下文的说法「直接把整个文件的内容糊到 OVPN 文件的末尾」,而应该转换一下格式为 ip route add 1.0.1.0/24 via 192.168.1.1 (其中 192.168.1.1 是你的默认网关 IP),并保存为一个 sh 文件,在启动 VPN 之前启动。

这样的好处在于不会像之前的写法一样,每次(可能由于各种原因)重启 OpenVPN 的时候 OpenVPN 需要手动删除所有路由,然后再一条条添加导致启动时间很长,而且重启期间会影响国内的网络访问。

转换格式的简单 Python 代码如下:

import ipaddress

def convert_subnet_mask(mask):
    return sum([bin(int(x)).count("1") for x in mask.split(".")])

with open("routes.txt", "r") as file:
    for line in file:
        line = line.strip().split()
        ip = line[1]
        mask = convert_subnet_mask(line[2])
        cidr = str(ipaddress.IPv4Network((ip, mask)).network_address) + "/" + str(mask)
        print("ip route add " + cidr + " via 192.168.1.1" )

直接把整个文件的内容糊到 OVPN 文件的末尾即可,如果后续需要将某个 IP 不走隧道,可以参考类似的格式加入一条记录并重启 OpenVPN。

此时,OpenVPN 的文件已经制作完成,如果你和我一样使用 Ubuntu 作为漏由器的话,把这个 some-vpn.ovpn 文件放到漏由器的 /etc/openvpn/some-vpn.conf 文件即可,然后…

systemctl start openvpn@some-vpn
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

哦对了,记得打开 ip_forward ,在 /etc/sysctl.conf 文件里面加入:

net.ipv4.ip_forward=1

sysctl -p

这个时候,你的漏由器应该已经可以开始漏由了,我们把电脑的网关设置成漏由的 IP 测试一下看看效果:

traceroute to 1.1.1.1 (1.1.1.1), 30 hops max, 60 byte packets
 1  _gateway (192.168.233.200)  0.347 ms  0.331 ms *
 2  * * *
 3  * * *
 4  * * *
 5  * * *
 6  * * * (**.**.**.**)  236.233 ms
 7  * * * (**.**.**.**)  231.722 ms
 8  * * * (**.**.**.**)  190.069 ms
 9  cloudflare-sgp.cdn77.com (45.134.215.21)  132.932 ms *  187.737 ms
10  172.70.140.5 (172.70.140.5)  187.430 ms 162.158.168.4 (162.158.168.4)  185.075 ms  192.811 ms
11  one.one.one.one (1.1.1.1)  192.735 ms  191.321 ms  194.676 ms

如果你希望你的隧道 IP 能像 Tor 一样动态切换的话,可以考虑将多个 OpenVPN IP 加到一个域名内,然后定期重启 OpenVPN。

Unbound DNS

下一步就是安裝 DNS 啦,非常简单,可以直接参考 「利用树莓派做旁路网关实现家居全局透明科学上网实践」一文,只不过 forward-zone 这里可以直接写一个合理的服务器地址了,比如 1.1.1.1

forward-zone:
        name: "."
        forward-addr: 1.1.1.1
        forward-first: no

DHCP

在以上操作完成了之后,修改路由器 DHCP 设置,下发网关和 DNS 为这个机器的 IP 即可。

通过以上步骤,你应该已经可以得到一个:

  1. 无污染的 DNS
  2. 可以 MTR 的自动分流的网络
  3. 一个公用出口减少部分 IP 关联

监控

不要梦中开车,在建立好服务后第一时间建好监控,至少监控以下指标:

  1. 漏由器到出口的延迟
  2. 漏由器到某个公共服务固定 IP 的延迟(比如 1.1.1.1),用来对比到出口的延迟
  3. 漏由器到某个不走 VPN 的公共服务固定 IP 的延迟(比如 101.6.6.6),用来参考境内网络情况

有了监控之后我们就可以检测不同的网络质量情况如何了。

后记

用了 OpenVPN 才知道这个东西的 Overhead 是真的大,同时也深刻感受到了,即使在网络正常的地区,跨洲拨 VPN 也是一个体验极差的事情,如果你本来的出口在日本,那就不要给自己找不快活去拨个瑞典的 VPN(除非你有什么特别的隧道)。

树莓派似乎真的不适合做漏由器,routes.txt 中 8000+ 条路由的添加速度非常的慢(启动 VPN + 改路由可能至少需要 20 分钟),不知道为啥。

References & Further Reading

  1. 利用树莓派做旁路网关实现家居全局透明科学上网实践
  2. Connect to OpenVPN over Shadowsocks
  3. Setting up Ubuntu Server 16.04 as Gateway for OpenVPN Connection

#Chinese #Network