连续两跳 VPN:究竟谁能看到你的流量?
串行跳转在第一道网关解密后再加密;嵌套跳转只剥离外层。逐一拆解两种设计,看清哪个运营方究竟能读到什么。
每个客户端配置只写一个服务器,所以从用户的位置看,只有一条隧道和一个保护终点。第一道网关之后发生了什么,是客户端永远看不到的设计决定,而两种常见设计在检查之下表现截然不同。在串行链中,第一道网关解密后再加密。在嵌套链中,它剥掉外层,转发一条自己打不开的内层隧道。两者的跳数完全相同,所以差别必须画出来,而不是数出来。
从客户端开始,把两个端点都命名
从用户起步的地方开始,也就是连接配置。它包含一个远端地址、一种认证方式,以及设计支持时的一份路由表。把从客户端到网关 A 的那条外层隧道标出来,并给它示例地址:客户端的隧道地址 10.77.0.2、A 的 10.77.0.1,以及 A 的公网端点 198.51.100.10,全部取自 TEST-NET-2 文档网段,这样示例里任何一项都不会与真实主机冲突。
那份配置里没有提到网关 B,这不是缺陷。第二跳属于 A,客户端唯一的义务是把流量送到 A,并接受返回的东西。A 是终结隧道、转发隧道还是新开一条,正是这趟走查余下部分要回答的问题,而它决定了 A 能读到什么。
串行:A 解密,然后再次加密
在串行设计中,A 两次终结流量。它先剥掉客户端的保护,拿到应用的 IP 包,再把该包送进 A 朝 B 建立的第二条隧道。在这两步之间,包以普通 IP 包的形式停在 A 的转发路径上,目的地是一个私有地址。
这带来两个值得直说的后果。A 能对内层包施加策略,形式是路由、过滤规则以及源与目的地的记录;同时 A 必须持有两套凭据,一套作为客户端的服务器,一套作为 B 的客户端。保护在每一刻都存在,但被保护的对象在 A 处换了身份。
这并不等于 A 读到了应用的明文。当客户端与服务都运行 TLS 并互相校验证书时,A 看到的是一条 TLS 记录流,而不是可读的请求。要确认那层保护在哪里结束,得看服务的证书链,而不是隧道示意图。
嵌套:A 只剥掉外层
在嵌套设计中,客户端先建立一条到 B 的内层隧道,再把这条隧道装在外层里送到 A。A 剥掉外层头部,发现一条内层隧道,在不持有其密钥的情况下转发它,于是内层保护仍然在 B 处终结。
这样一来,客户端的上行链路承载两层,而 A 与 B 之间的路径只承载一层。A 能看到内层隧道的外层地址,足以知道流量存在、大致去向,却不足以读到载荷。这就是与串行情形全部的差别,而没有任何跳数计数器能显示它。A 仍能做的是拒绝转发自己读不了的东西,这一点对那道网关的运营方始终开放。
嵌套要为这个性质付出代价。每一层都增加头部开销,于是 MTU 与 MSS 的规划从整洁变成必须,一条单层能走的路径在双层下可能悄悄丢大包。每一层还需要各自的重新连接与保活行为,而一层里的故障,透过另一层看起来就像局部中断。
画出观察者,而不只是隧道
隧道是这张图里容易的部分。设计真正讲的是观察者,所以按每个观察点写一行,并填上每一方实际能读到什么。
| Vantage point | Serial chain | Nested chain |
|---|---|---|
| Client's access network | Outer tunnel only | Outer tunnel only |
| Operator of A | Inner IP packet between A and B | Outer addresses of the inner tunnel, not its payload |
| Path between A and B | Second tunnel | Second tunnel |
| Operator of B | Inner packet re-originated by A | Payload after the inner layer is removed |
| Service edge | Traffic from the exit address | Traffic from the exit address |
有两行总被混为一谈:A 的运营方,和客户端隧道的提供方。它们可能是同一个实体,网关名字不同并不让它们彼此独立。真正的独立所有权需要独立的行政控制、独立的凭据和独立的日志。
元数据在两种设计下都活着。时序、包大小和连接数在每一跳都仍然可观测,所以两跳提高的是关联的成本,而不是消除信号。一个承诺不可见的方案,在有人依赖它之前,应该先对照这份清单检查。
选择之前先把失败路径走一遍
从客户端一侧故意弄坏东西,按用户会遇到的顺序来。先从接口和它的 MTU 开始,因为嵌套在包大小上失败,远早于它在认证上失败。
ip -4 addr show dev tun0
ip link show dev tun0 | grep mtu
ping -M do -s 1380 -c 2 10.77.1.1
接着去确认保护的端点,而不是假设它们。从任意一条已建立的隧道内部向服务索取它的证书,把客户端看到的链与服务实际出示的链做比较。重新签名或拦截的网关会改变那张证书,检查只要一条命令就能看见。
openssl s_client -connect intranet.example.com:443 -brief < /dev/null
最后是重连矩阵:客户端隧道断、B 的隧道断、A 与 B 之间的路径变化。记下什么恢复了、什么没有,以及内层连接是否会掉到预期承载之外的路由。记下每次测试所用的地址,因为换一个接入网重做,回答的是另一个问题。每次重连之后也要重做 MTU 探测,因为路径变了,可能在配置一行没动的情况下压低可用尺寸。
当 A 必须对内层包施加策略,或客户端跑不了两条叠起来的隧道时,选串行。当内层载荷必须在 A 处保持不可读时,选嵌套。把选择和它的观察者、它的 MTU 数字一起写下来,下一个人就不必为了回答一个问题而重画整张图。