高可用性翻墙路由:2. VPN 的搭建
为了翻出墙外,VPN 是必不可少的。你需要购买至少一个国外 VPS 作为出口。而为了高可用性,建议购买至少两台国外 VPS。它们最好是不同线路,不同地区的,以便在某一个服务质量下降时使用另一个作为备份。而国内最好也有一台 VPS 作为流量中转。VPS 应装有 GNU/Linux 操作系统,这里建议直接选择 Debian。
当你有了两台国外 VPS 和一台国内 VPS 后,可以以国内 VPS 为中心建立星型 VPN 网络。每个设备都与国内 VPS 建立一个 P2P 隧道。隧道两端地址可以选择一个 /31
的子网。隧道之间的地址不要重合,例如:
- 国内 VPS(
10.0.0.0/31
) ↔ 家(10.0.0.1/31
) - 国内 VPS(
10.0.1.0/31
) ↔ 国外 VPS1(10.0.1.1/31
) - 国内 VPS(
10.0.2.0/31
) ↔ 国外 VPS2(10.0.2.1/31
)
VPN 方案的选择
IKEv2 + IPSec
IKEv2 + IPSec 是最标准的 VPN 方案,所以几乎所有的终端与路由器都对其有良好的支持。这还产生了其过墙能力很好的都市传说,原因是很多大公司都在使用它。其中 IKEv2 协议负责协商网络两端的通信参数,而 IPSec 负责传输数据负载。
IKEv2 + IPSec 也支持漫游,即网络的一端可以不具备固定的 IP 地址,甚至可以随时切换地址(例如从 Wi-Fi 切换到移动网络)而仍然保持连接。
WireGuard
WireGuard 固定用 UDP 传输,并使用 ChaCha20-Poly1305 加密。配置起来比 IPSec 方便一些。据其官网评测,无论在延迟还是吞吐方面,WireGuard 都超越了其他方案。
WireGuard 也支持漫游,这得益于 WireGuard 只通过公钥,而不是对端地址来认证彼此。一旦某一个 IP 地址发来的包通过了公钥验证,后续向对方发回去的包都会选择这个 IP 地址,而不是静态配置中的地址。
WireGuard 比较新,所以大部分硬件路由器是不支持的。好在 pfSense 是支持 WireGuard 的。
OpenVPN / PPTP / L2TP
这些协议基本都不推荐。它们要么无法用于穿墙,要么性能低下,要么配置复杂,要么以上缺点都具备。
IKEv2 + IPSec
IPSec 封装
IPSec 分为隧道模式与传输模式。隧道模式在外层 IP 包中包裹需要传输的内层 IP 包,可用于桥接两个子网。而传输模式只会封装内部数据,没有额外的 IP 包头,只能用于保护两个主机之间的直接通信。传输模式过去曾被计划用于 IPv6 的加密,目前多用于封装 L2TP 协议。1 这里我们将要用到的是隧道模式。
每种模式又可以选择 AH 或 ESP 封装。AH 只提供数据认证,不能加密数据,而 ESP 可以同时提供数据加密与认证。另外,ESP 封装可以自动侦测路径上的 NAT 设备,从而自动改用 UDP + ESP 穿越 NAT。所以我们选择 ESP 封装。
基于策略 vs. 基于路由
在配置方面,IPSec 又可分为基于策略与基于路由两种配置方式。
默认的是基于策略的方式,即数据包都经过同一个底层网络接口发出,但根据其源、目的地址的不同,在发出前选择封装到某一个 IPSec 连接中。这种方式适合于简单的网络拓扑,例如对于北京子网的包发送到北京的网关,而上海子网的包发送到上海。
而如果两个节点之间不只有一条路径,而需要动态选择最优路径的话,基于策略的配置无法与 BGP、OSPF 等路由协议良好地配合在一起。这时可以采用基于路由的配置方法。基于路由的配置会给每个 IPSec 隧道建立一个虚拟网络接口,之后在策略中规定凡是经过此网络接口的数据包都走某一个 IPSec 隧道。这样我们就可以在路由表中配置不同 IP 段路由到不同的虚拟接口,而无需再关心 IPSec 策略。
需要注意到的是,无论是基于策略还是基于路由,底层网络上传输的数据包是一样的。因此甚至可以在隧道的一端使用基于策略的配置,而在另一端使用基于路由的配置。
防火墙
首先确保两端的各种防火墙都允许了以下协议通过:
- ESP,即 IP 协议号50。
- UDP 500,用于 IKEv2 协商密钥。
- UDP 4500,用于 UDP 封装的 ESP 通过 NAT,即 NAT-T。
其中 UDP 500 是必须的。如果两台主机间存在 NAT,则 UDP 4500 是必须的。如果没有 NAT,则要么允许 ESP 通过,要么允许 UDP 4500 并开启强制 NAT-T 封装。
这里判断是否有 NAT 的简单方法是通过 ip a
命令看两端网卡上绑定的地址,如果都是公网地址,则没有 NAT,否则会有 NAT。一些云计算平台会将机器放置在 VPC 中,机器只能得到 VPC 中的私有地址。虽然此时会有公网地址与这些私有地址一一对应,但也属于存在 NAT 的情况。
IPSec
安装 strongSwan,各大发行版都有现成的软件包。如果是 Debian 的话,直接 apt install charon-systemd
即可。
strongSwan 有两种配置方式:
- 第一种是用
ipsec.conf
和ipsec.secrets
作为配置文件。同时使用ipsec
命令行控制连接,使用starter
作为后台服务。这种方式已被淘汰。 - 第二种是用
swanctl.conf
文件以及swanctl
目录作为配置。同时使用swanctl
命令行控制,使用charon
作为后台服务。这是目前推荐的方式。
因此对于 Debian 系统,最好卸载 strongswan
软件包,只安装 charon-systemd
,否则可能引发冲突。
之后生成两端的通信密钥,这里为了方便采用 PSK 方式认证。如果节点繁多,为了方便管理,也可使用基于证书的方法认证。证书链的配置方法在 strongSwan 文档中有介绍。
$ dd if=/dev/random count=1 bs=33 2>/dev/random | base64
s1Jy/Li0Bkqmd3C7eYG71EoPxmcataF/LL4ATA3gE+au
修改 /etc/swanctl/swanctl.conf
为如下内容:
connections {
<对端名称> {
version = 2
remote_addrs = <对端地址>
local {
auth = psk
id = <对端ID>
}
remote {
auth = psk
id = <本端ID>
}
children {
all {
local_ts = 0.0.0.0/0
remote_ts = 0.0.0.0/0
start_action = trap
}
}
if_id_in = <接口ID>
if_id_out = <接口ID>
}
}
secrets {
ike-<对端名称> {
id = <对端ID>
secret = 0s<PSK>
}
}
<对端名称>
可以随意选取,只用于配置文件内标识不同的连接。<本端ID>
,<对端ID>
用于身份认证,可以随意选取,但网络两端必须恰好相反。<对端地址>
填写对端的公网 IP 或域名。也可以填写多个,使用逗号分隔。<接口ID>
是一个任意整数,用于关联 IPSec 连接与隧道网络接口。不同连接的接口ID不能重复。secret
一项要在上一步生成的 Base64 格式 PSK 前面加上0s
前缀,例如0ss1Jy/Li0Bkqmd3C7eYG71EoPxmcataF/LL4ATA3gE+au
。
如果网络一端没有固定 IP,例如家庭宽带连接到 VPS,则对具有固定 IP 一端的配置做如下修改:
- 删除
remote_addrs
一行,使其接受任意对端 IP。 - 删除
start_action = trap
一行,使其不会主动发起连接。
完成后运行 swanctl --load-all
加载配置。
XFRM
XFRM 是 Linux 4.19 后支持的虚拟网络接口类型,相比之前的 VTI 接口配置更为简单直接。每个 XFRM 接口有一个接口ID,系统正是通过接口ID,将出入接口的网络流量关联到 IPSec 连接。这里简单起见,我们使 XFRM 接口与 IPSec 连接有1:1的对应关系。
如果是 Debian 系统,并且用 ifupdown 管理网络,则在 /etc/network/interfaces.d
中新加入一个网络接口即可:
auto xfrm0
iface xfrm0 inet static
address <隧道本端IP>
netmask <隧道子网掩码>
pre-up ip link add xfrm0 type xfrm dev <底层网络接口> if_id <接口ID>
post-down ip link del xfrm0
mtu 1410
<隧道本端IP>
是 VPN 隧道本端的 IP 地址,例如开头例子中的10.0.1.0
。<隧道子网掩码>
是 VPN 隧道的子网掩码。如果采用/31
的子网,则子网掩码为255.255.255.254
。<底层网络接口>
是可以访问互联网的网络接口名称,例如eth0
。
MTU 的设置可以参考这篇文章。这里我采用的数值 1410
是考虑了常见最坏情况下的数值。其计算过程如下:
封装 | 字节数 |
---|---|
PPPoE | 8 |
IPv6 | 40 |
UDP (NAT-T) | 8 |
ESP Header | 8 |
ESP IV (AES-GCM) | 8 |
ESP Trailer | 2 |
ESP Authentication (AES-GCM) | 16 |
总计 | 90 |
MTU | 1500 - 90 = 1410 |
完成后运行 ifup xfrm0
打开 XFRM 接口。
调试
如果你足够幸运,此时在隧道一端 ping
另一端隧道内 IP 可以连通。否则可以尝试如下方法逐步调试。
- 确认对端公网 IP 可以
ping
通。 - 运行
ip xfrm policy
查看有没有相应的 IPSec 策略。 - 运行
journalctl -f -u strongswan
或swanctl --log
。观察其输出的握手过程。 - 运行
swanctl --list-sas
打印正在建立或已建立的连接。 - 运行
ip route
打印路由表,以及ip route get <IP>
打印特定 IP 的路由选择。 - 运行
tcpdump -i <网络接口>
抓包观察。
WireGuard
主流的发行版同样提供了 WireGuard 的软件包。如果是 Debian,则直接 apt install wireguard
;如果是 pfSense,则在 System » Package Manager 中安装 WireGuard
包;其他系统请参考官网的安装说明。
WireGuard 只需用到一个 UDP 端口,且端口号可以任意指定。相互连接的两端只要有一端具有公网 IP 以及可接受入站连接的 UDP 端口即可。另一端即使位于 NAT 后且不可接受入站连接也没有问题。这里我们假设使用了默认的 UDP 端口 51820。注意在防火墙上放通此端口。
单网络接口 vs. 多网络接口
WireGuard 支持通过同一个网络接口与多于一个 Peer 通信。其实现方式是要求同一个网络接口中各个 Peer 的 AllowedIPs
不重合。这样当发送一个 IP 包时,就可以根据 AllowedIPs
自动选择向哪个 Peer 发送。但这样的问题在于无法做到动态选择路由。
例如有三台主机 A、B、C,它们各自把另外两台主机加入自己的 Peer 列表中。如果 A 希望与 C 通信,那么它只能向 C 发送数据包,而不能绕道 A → B → C。在一般情况下这是没问题的,但如果 A 与 C 之间无法直接正常通信,就会导致两者之间完全断联。
因此我们采用每个 WireGuard 网络接口只设置一个 Peer 的方式。此时只需把 AllowedIPs
设为 0.0.0.0/0
和 ::/0
即可。而对于 VPN Mesh 中的路由问题,我们在下一篇文章中通过 OSPF 解决。
Linux
在 Linux 下,首先使用 wg genkey | tee /dev/stderr | wg pubkey
生成一对私钥与公钥。之后新建一个配置文件 /etc/wireguard/wg0.conf
,并写入如下内容:
[Interface]
PrivateKey = <本端私钥>
ListenPort = 51820
[Peer]
PublicKey = <对端公钥>
Endpoint = <对端IP>:51820
AllowedIPs = 0.0.0.0/0, ::/0
如果对端没有公网 IP 与端口,其中 Endpoint
一项可以不填。如果本端在 NAT 后面,建议在 [Peer]
一节中添加 PersistentKeepalive = 25
,以便维持 NAT 连接映射。
在使用 ifupdown 的系统中,在 /etc/network/interfaces
中加入一个网络接口:
auto wg0
iface wg0 inet static
address <隧道内本端IP>
netmask <隧道内子网掩码>
pre-up ip link add wg0 type wireguard
pre-up wg setconf wg0 /etc/wireguard/wg0.conf
post-down ip link del wg0
两端运行 ifup wg0
就可以开启隧道。此时可以运行 wg
命令查看连接状态,或 ping <隧道内对端IP>
验证其是否正常工作。
pfSense
当安装完 WireGuard 包后,在 VPN » WireGuard » Tunnels 就可以新建一个 WireGuard 网络接口。其中 Interface Addresses 可以先不填,而是在创建完成后再去 Interfaces » Assignments 中手动给刚建好的 tun_wg0
关联一个新的网络接口并设置静态 IP。同时也要将 MTU 和 MSS 设为1420。
之后在 VPN » WireGuard » Peers 添加 Peer。注意将 Allowed IPs 设为 0.0.0.0/0
和 ::/0
。
防火墙方面,需要分别设置 WAN 的规则与 WG0 的规则。两者都在 Firewall » Rules 中。对于 WAN,需要允许 any 访问 This Firewall 的 UDP 51820 端口。对于 WG0,至少允许 ICMP 协议以便测试连通性。
配置完成后可以在 Status » WireGuard 界面看到连接状态。并在 Diagnostics » Ping 页面中测试到对端的连通性。
上一篇文章:高可用性翻墙路由:1. pfSense 的安装
下一篇文章:高可用性翻墙路由:3. OSPF 与动态路由