为什么许多应用连接共用同一个 OpenVPN 会话
一条外层 UDP 会话承载路由送入其中的每一条内层流。内层端口与协议保持不变,只有外层五元组固定不变。
想象一个浏览器正在加载 HTTPS 页面,一个 SSH 客户端正在连接另一台服务器,一个解析器正在回答一次查询,全部走同一条 OpenVPN 会话。在物理网卡上你只看到一段对话;在隧道内部你看到三段。两种视角都对,而知道为什么,决定了抓包是有效还是令人困惑。把一条内层流从套接字追到网线上,分层就不再抽象。
一条外层对话,多条内层流
每个应用打开自己的套接字,内核给它一个由源地址、源端口、目的地址、目的端口和协议组成的五元组。这些五元组在任何隧道介入之前就已经存在。随后路由决定它们全部从隧道设备发出,因此交给 OpenVPN 的数据包内部仍带着原始首部。OpenVPN 加密这条内层数据包,并把结果封装成与对端的一条外层 UDP 或 TCP 对话。所以外层对话只有一条,而其中的流有多少条,取决于应用打开了多少条。
什么都没有合并。内层五元组端到端保留下来,正因如此,远端仍然能区分这三条流。
保持不变的那个端口
保持不变的是外层五元组:你的地址、客户端绑定的本地端口、对端地址,以及配置的服务端口。路径上的防火墙或 NAT 必须放行的就是它,上行链路抓包看到的也是它。而在重连或切换网络之后发生变化的同样是它,因为客户端可能绑定另一个本地端口。一条隧道并不意味着源端口可以永远固定不变。
端点关心的是内层端口。即使外层传输坐在运营商配置的端口上,HTTPS 流在隧道内部仍保留目的端口 443。应用的端点从不知道外层端口的存在。
解封装在远端改变了什么
在对端,外层被剥离,剩下的是内层数据包。从那里开始,转发与访问策略作用于内层目标和协议。Web 服务器仍然收到 HTTPS 连接,SSH 服务仍然收到 SSH,解析器仍然收到一次查询。
这就是为什么放行外层传输的规则,并不等于放行某项内部服务的规则。前者描述的是一条路径,后者描述的是一项授权。
外层是 TCP 意味一条流,而不是一条连接
当外层传输是 UDP 时,每条内层流的命运彼此独立:一个丢失的数据报只会影响一次传输,直到它自己重传。当外层传输是 TCP 时,所有内层流共用一条可靠的字节流。
这条共享字节流正是人们容易忽略的部分。恢复只发生一次,而且是为一件事整体发生,所以一个丢失的报文段会拖慢内部复用的每一个应用的流量。端点处仍然是许多条连接,而线路上只有一条连接,队头阻塞效应就活在这条线路上。监听端口的数量对此什么也说明不了,也正因如此,靠数端口来推断隧道性能是很差的方法。
准确描述你自己的配置
请说出所选的内部流量,以及承载它的具体外层传输,而不是笼统地说这台机器把所有东西都从一个端口发出去。本地服务、第二条隧道和另一个地址族,都会产生从不进入这段对话的流。上行链路的抓包显示外层五元组;隧道内部的抓包显示内层五元组。
两个抓包都做,并把时间戳并排对比。如果你需要证明哪个应用产生了哪条内层流,主机上的套接字表是更好的工具,因为它把端口直接对应到进程。随后请记下你观察到的外层五元组和观察的时刻,因为重连之后的下一份抓包完全可能合理地显示另一组数字。