一台 Linux 机器上的两个 VPN:分别测试,不混淆结果
两个绿色图标并不等于一次对比。把每个隧道放进各自的命名空间,先写下预期,再证明实际路径,然后才相信结果。
两个绿色的连接图标看起来像一次对比,通常却只是巧合。如果这次测试请求本来可以用上任意一条隧道,那么结果最好的情况也只描述了其中一条,最坏的情况一条都没描述。 给每一次观察配上网络上下文、预期目的地,以及在测试运行之前就写下的预期。在自己管理的 Linux 机器上,具名的网络命名空间是买下这份隔离最便宜的办法。
两盏绿灯并不等于一次对比
命名空间会隔离路由表、接口和网络栈的状态,因此在其中一个里发起的请求,无法悄悄借用另一个的隧道。它不隔离的是处理器、物理上行链路,以及跑在宿主机上的解析器守护进程。恰恰是最后这个缺口,让大多数此类测试在无人察觉的情况下失败。一个离开命名空间、去问宿主机解析器的查询,返回的答案与正在测试的隧道毫无关系。
本流程假设已有两个预先准备好并获得批准、外部连通正常、客户端受支持的测试命名空间。它不创建任何东西,不登记任何东西,也不替换任何配置。当客户端需要完整的桌面会话,或拒绝在桌面会话之外运行时,独立的虚拟机仍然是诚实的做法。
在第一次运行之前就固定预期
逐个上下文决定成功的样貌:解析器、服务名、预期地址,以及隧道断开时必须出现的行为。把这些计划中的值放在单独一列,因为把计划和观察混在一起的记录,只是带时间戳的虚构。下表的检查清单就是这份记录需要的样子,每一行都必须在第一次连接之前就能填好。
| Tunnel | Owner | Client interface inside the namespace | Proof command | Required on disconnect |
|---|---|---|---|---|
| vpn-a | the identity that owns profile A, recorded by name | its own tun device, never the host's | ip -n vpn-a -4 route get 10.55.10.20 | traffic stops; no host-side fallback |
| vpn-b | a separate owner, even when both run on one box | its own tun device in its own namespace | ip -n vpn-b -4 route get 10.55.20.20 | verified separately, never inferred from A |
解析器的接线在记录里值得单独占一行,因为它是最容易撒谎的字段。与命名空间绑定的文件,例如 /etc/netns/vpn-a/resolv.conf 在命名空间内执行命令时会出现在惯常的路径上,正如 iproute2 的命名空间参考 所描述的那样;但名称服务切换的配置,以及它启动的任何本地解析器,仍可能把一次查询拉回宿主机。
一次只开一条隧道,另一条关掉
B 保持关闭,通过它现有的所有者把 A 连上,然后从管理上下文查看策略、路由和选中的路径。下面三条命令只是读取状态;只有最后一条会联系名称服务,而且没有一条会发出应用请求。
sudo ip -n vpn-a -4 rule show
sudo ip -n vpn-a -4 route show table all
sudo ip -n vpn-a -4 route get 10.55.10.20
sudo ip netns exec vpn-a getent ahostsv4 svc-a.lab.internal查路由回答的是关于目的地的假设性问题,因此它无法证明服务真的接受流量。把这两类证据放在不同的列里,因为一条健康的路由旁边跟着一个被拒绝的连接,是真实而常见的结果。然后用真实应用发出一个无害且已获授权的请求:在 A 的上下文里以它平常的非特权用户运行,证书校验与应用层认证保持原样,并把结果连同服务看到的源地址一起记下来。通过所有者断开 A,再对 B 重复整条基线,并记下你做过的任何配置改动,否则两条基线就不再可比。
两条都开,然后一次关一条
稳态是容易的部分。两条隧道都连着的时候,重复每个上下文的查询和应用请求,然后断开一条隧道,检查另一条。当连接顺序本身就是问题时,再按相反顺序重连。
sudo ip netns exec vpn-a tcpdump -ni any port 443 -c 5
sudo ip netns exec vpn-b getent ahostsv4 svc-b.lab.internal
sudo ip -n vpn-b -4 route get 10.55.20.20抓包能解决应用结果解决不了的争论:它能显示请求是从你预期的接口出去的,还是从完全别的东西出去的。每一次转换都记下所有者的动作、时间戳、路由结果、名称结果和应用结果。然后检查受影响命名空间内部的失败即关闭要求,因为隔离并不等于急停开关。命名空间把两条隧道分开;当它的配置写明流量必须停止时,它并不会阻止其中任何一条继续承载流量。
这份记录能声称什么,不能声称什么
一次通过只证明这套安排在本次主机、那个时刻的这些观察。它对共用一张桌面路由表的两个客户端什么也说明不了,也对两条隧道同时承载负载时的吞吐量什么也说明不了。那些是需要各自测试的另外的问题,而一次对比的成色,取决于每一行附带的上下文。
把失败和成功并排留在记录里,标注当时哪条隧道是通的。一页把测量时状态藏起来的干净记录,比一页把它显示出来的丑陋记录更糟。让这份记录日后可以复用的,是上下文,不是分数。
一台机器上的两条隧道可以诚实地比较,但只限于你真正控制过的那几个上下文。超出的部分,只是一个旁边挂着绿色图标的愿望。