你需要决定自己的 VPN 实验环境配得上多少机器。两台 VirtualBox 网段之间的一台路由网关 VM,会复现地址转换、回程路径,以及真实客户端所坐的防火墙位置。单个仅主机网段只给你一扇受控的门,别的什么都没有。网关版需要三台客机和一张由你维护的路由表;仅主机版只需要一台客机和一条策略。三个问题把两者分开,而第三个问题决定你的测试究竟能不能通过。

在克隆任何东西之前先回答三个问题

从 VPN 服务器必须观察到什么开始。如果服务器必须看到一个不属于测试客机的源地址,那么两者之间必须有东西做转换,而那个东西必须是你控制的路由器。仅主机网络给主机在客机网段上的是一块网卡,而不是转换器。当客机随后需要访问外部资源时,你会接上一块 NAT 网卡,而 VirtualBox 自带的 NAT 引擎会在不询问你的情况下重写源地址。

接着问客机是否只能通过隧道访问其他一切。这个答案决定了用内部网络还是仅主机网络,而多数实验文章恰好跳过这个问题。最后问隧道掉线时必须发生什么,因为它决定的是策略而不是拓扑;一个只证明顺境的实验几乎什么都没测。

What the test requiresRouted gateway VMHost-only guest
Server sees a translated source addressYes, from your own MASQUERADE ruleNo, VirtualBox NAT does it instead
The guest has exactly one egressYes, while the second adapter faces the tunnelOnly until a NAT adapter is attached
Policy is testable during a tunnel failureRules live in one placeRules live on each guest

用内部网络连接三个角色

使用三台客机和两个具名网段,并让网关成为唯一在两段上都有网卡的机器:

VBoxManage modifyvm lab-client --nic1 intnet --intnet1 WAN
VBoxManage modifyvm lab-server --nic1 intnet --intnet1 LAN
VBoxManage modifyvm lab-gw --nic1 intnet --intnet1 WAN --nic2 intnet --intnet2 LAN

内部网络没有单独的创建步骤。--intnet1 里的字符串就是网段本身:两台机器只有在名称完全一致时才共享它,而主机上没有任何东西接在上面。最后这一点正是实验可以干净复现的原因,因为主机的路由、它自己的 VPN 客户端以及它的防火墙都不可能意外地摸到客机。内部网段也没有 DHCP 服务器,除非你自己加一台,所以静态地址是最短的路径。

给客户端只配它自己的 WAN 地址,并让默认网关指向网关的 WAN 地址。服务器拿到一个 LAN 地址,并把网关的 LAN 地址当作自己的默认网关。如果一台客机在同一网段上留了两个地址,或者有一条指向另一网段的游离路由,它就有一条绕过路由器的路径,而下面的测试会对你撒谎。

我在 VM 名称旁边放一个文本文件,记下四个地址和两个网段名,并把它粘贴到每个 shell 里。几周之后,当某次配置变更弄坏了东西,那个文件就是调试配置和调试我自己的记忆之间的差别。

对不该泄漏的网段选择内部网络或仅主机网络

仅主机模式会在主机自身上创建一块虚拟网卡,通常是 vboxnet0,并给主机在客机网段上一个地址。这对管理很方便,对隔离测试却是致命的,因为主机现在可以直接访问客机,主机侧的每一项服务都成了一条可能的路径。内部模式则完全没有主机网卡。两种模式在网络设置面板里很容易混淆,所以要核对文档化的差异,而不是看标签(参见 VirtualBox 网络细节 页面)。

对必须使用隧道的客机来说,内部网络是更安全的默认值,网关是唯一的出口。如果你保留仅主机模式,就把主机网卡当作管理路径,并大声说出来:主机可以访问客机,而客机不应该能把主机当作出口。

VBoxManage modifyvm lab-client --nic1 hostonly --hostonlyadapter1 vboxnet0

你能证明的隔离,就是你配置出来的隔离。只有一块内部网卡、默认路由指向网关、且没有 NAT 网卡的客机,没有任何绕过网关的路径。同时拥有仅主机网卡和 NAT 网卡的客机有两条路径,而其中只有一条是你想要测试的那条。

在网关上做路由和转换,然后让它失败时关闭

VirtualBox 不会在网段之间转发,转发的是客机。当 VPN 服务器 VM 位于网关之后时,打开转发,并在客户端流量出向服务器时做地址转换:

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

有了这条规则,服务器看到的是网关的 LAN 地址而不是客户端的地址,这正是运营商 NAT 后面的真实客户端在网头看来该有的样子。把 sysctl 值保存到 /etc/sysctl.d/ 里,把规则保存到发行版所用的防火墙服务里,否则一次重启就会悄悄把实验退回到已路由但未转换的状态。

转发不是授权。在 ACCEPT 的默认 FORWARD 策略下,默认路由指向网关的客机会从路由表选中的那块网卡出去,不管有没有隧道。如果网关还为装软件包保留了一块 NAT 网卡,那么隧道掉线的那一刻,那块网卡就是一条可用的明文出口,而你的仅 VPN 测试从来就不是仅 VPN 测试。

当服务器在远端、由网关自己终结隧道时,同样的决定指向隧道接口,而不是那块通向互联网的网卡:

iptables -P FORWARD DROP
iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

把隧道自身的外层传输和带外管理路径与被转发的流量分开。如果修网关的唯一途径是经过你正在调试的那条隧道,实验就变成了一次把自己锁在门外的练习。串口控制台、仅主机管理网卡,或者一个已知良好状态的快照,都比手工恢复一台坏掉的路由器便宜。

从客机而不是从主机验证路径

从内向外做。先确认客户端能到达网关的 WAN 地址,再确认它能经网关到达服务器,再确认到 VPN 端口的连接能出去并回来。在网关两块网卡上抓包,可以看出应答是否到达、是否在回程上被丢弃,而只在客户端抓包无法区分这一点:

tcpdump -ni eth0 udp port 1194
tcpdump -ni tun0 udp port 1194

然后证明出口。从客机向地址回显服务询问它看到的源地址是什么,并与网关自己的出口地址比较;如果答案与主机的地址一致,就存在一条绕过隧道的路径。检查客机实际使用的是哪个解析器,并单独检查 IPv6,因为一个启用了 IPv6 而隧道只承载 IPv4 的网段是按设计泄漏,而不是偶然泄漏。

最后,故意把它弄坏。停掉隧道,或者封掉它的外层端口,然后从客机发起一条新连接。它必须失败,而且理由必须是你叫得出名字的:没有路由、有一条拒绝规则,或者解析器已经死了。重新启动隧道,确认客机在不重建网络的情况下恢复。如果像 Hamachi 或 ZeroTier 这样的 mesh VPN 共享了这个实验,请记住桥接网卡跟随的是网卡名称而不是 mesh,所以客户端安装了自己的虚拟网卡之后必须重新指向桥接。

VBoxManage list bridgedifs
VBoxManage modifyvm lab-client --bridgeadapter1 <mesh-adapter-name>

把客机的隧道与主机共享,只是把问题照了一遍。把主机的默认路由指向客机是可行的,在客机里放一个代理也可行;经主机做 NAT 并不会为主机发起的流量承载客机的隧道。把网段名和防火墙规则保存到一处,给能用的状态拍个快照,并记录服务器观察到的源地址。这样下一次配置变更的代价就只是一次脚本运行,而不是重建实验。

相关文章