一次 SSH 会话、一个 Web 应用和一次域名查询可以共用同一条 VPN 连接,而不会争抢端口。客户端只向网关建立一条加密流,所有应用会话都在其中传输。从外部只能看到隧道自身的传输协议与端口。在内部,每项服务仍保留自己的端口,也仍需要各自的授权。

外层流承载什么,又隐藏了什么

客户端在数据包离开本机之前,会把它封装进隧道的传输层。这条外层流只有一个目的端口,在配置 VPN 时就已固定。在封装内部,每个原始数据包仍保留其所指服务的地址与端口。网关剥掉封装后按内层包头路由,因此目标端看到的只是一个从网络中来的普通客户端。

所以在 WAN 接口上抓包只会看到一条会话,而不是三条。内层端口只有在解封装之后才会出现。正因如此,一个防火墙开口就能同时承载 SSH、HTTPS 和 DNS。

# WAN side: one outer tuple, whatever the inner services are
tcpdump -ni wan0 'udp port 443'
# tunnel side: the inner tuples appear after decapsulation
tcpdump -ni tun0

TUN/TAP 文档介绍了这种交接:经过路由的 TUN 设备在内核与用户空间进程之间传递 IP 数据包。正是这次交接,使内层包头对网关的路由与防火墙代码可见。它也解释了为什么外层端口并不说明允许哪些服务。

NAT 转发属于监听端,而不属于应用

位于 IPv4 NAT 之后的自建网关只需要一条入站转换:把公网地址和端口映射到 VPN 监听。仅因为隧道用户可以访问 SSH、HTTPS 或 DNS,并不需要为它们另开规则。把这些端口发布出去,反而会暴露隧道本已保护的服务。转发规则描述的是监听端的可达性,而不是对内部任何东西的授权。

反过来同样成立。打开隧道端口不会暴露其他东西,因此只能经隧道访问的服务,其公网表面仍然关闭。这种分离通常正是把服务保持私有的原因。

访问权限其实是在内层策略中决定的

加密保护的是路径,并不为目标授权。客户端路由决定哪些前缀进入隧道,网关的转发策略决定数据包能否离开,目标端的防火墙与登录则决定服务是否应答。DNS 是最清楚的例子:承载查询并不等于让解析器接受来自你客户端地址池的查询。每一层都可以独立地说不。

通过隧道逐项测试每项服务。查询成功并不能说明 Web 端口可用,Web 请求成功也不能说明 SSH 可用。

dig @10.40.0.53 app.corp.example
nc -vz 10.40.0.10 8443
ssh -p 2222 [email protected]

当隧道正常而一切都失败时,先读客户端路由表。当某一项服务失败而其他服务正常时,故障就在那个目标上。这两种观察指向不同的责任方,把它们混为一谈只会浪费时间。

WireGuard 划的是同一条线

WireGuard 把自己的外层监听命名为 ListenPort,而内层应用仍然使用它需要的服务端口。AllowedIPs是一组被路由的前缀,而不是允许的服务端口清单。把它当成端口白名单来读,写出的规则就与隧道的行为不一致。命名不同,分层不变。

服务运行在端点本机上时,就在本地输入策略中放行;位于该端点之后时,就在转发策略中放行。这个区别决定了哪个规则集能看到数据包。

总体形态是一条外层传输承载多条内层会话。需要多次外层交换或另一种 IP 协议的产品提醒我们:单一公网端口这一模式描述的是这种设计,而非所有 VPN。

相关文章