TLS 走在控制通道内部,而不是整条隧道
对等方交换各自的报文,TLS 在控制交换内部运行。一个只懂 HTTPS 的代理根本不知道这种流量是什么。
在 VPN 描述里看到 TLS 这个词,就断定 Web 代理能理解这类流量,是一种常见的偷懒。在这个协议内部,对等方交换的是各自的报文,TLS 只是那次交换中某一条通道的一部分。用户真正关心的载荷以另一种消息格式传输,因此两者永远不会与普通的 HTTPS 会话对应得上。
先有外层信封
每个隧道报文都以协议自己的头部开始:消息类型、密钥标识,以及在字节流传输上标记报文结束位置的长度前缀。不解析这层信封的读者,无法知道下一个字段从哪里开始。
outer transport (UDP datagram or TCP stream)
-> OpenVPN header (opcode, key id, length where needed)
-> control message (reliability wrapper, TLS exchange)
-> data message (protected inner packet)
这张草图是地图而不是字节表,因为具体字段取决于消息类型,尽管第一个八位组总是把操作码和密钥标识打包在一起。用端口号去猜消息类型,正是解码器最终把看到的东西标错的原因。对诊断来说,顺序才是关键:先是传输,然后是协议信封,最后是两类工作之一。在形成关于后续内容的判断之前,先读信封。从错误位置开始的解码器会产出自信的胡言乱语。
可靠性层属于协议本身
控制消息需要有序、被确认的投递,协议用确认和重传自行提供这一点。确认可能作为独立消息到达,也可能搭在另一条控制消息上,这让期待一次干净交换的人觉得抓包轨迹很不规则。
好处是即使外层传输是 UDP,TLS 握手也能得到可靠的承载。代价是单个数据报可能只包含那段会话的一部分,因此把它当作独立的 TLS 记录来处理只会得到胡言乱语。重组必须发生在协议层。这就是抓到的报文很少看起来像完整握手步骤的原因。
把外层传输换成 TCP 并不会去掉这一层。可靠传输意味着协议层重传更少,而不是帧结构消失,也不是流量变成 HTTP。
数据报文使用另一种格式
会话建立之后,有用的载荷以数据消息的形式到达,承载内部 IP 报文或以太网帧。它们用各自的密钥和各自的处理来保护,绝不是 TLS 库会接受的那种 TLS 应用记录。
这种区分在配置里就能看到:控制通道和数据通道有各自的密码设置,所以提到其中一个的日志行并不能说明另一个。把强控制密码当作强数据密码的证据,是常见的报告错误。
还要把一个相关误解挡在解释之外。TLS 不是坐在对称数据通道旁边的公钥通道,因为密钥确立之后,TLS 自身对记录也是对称保护的。真正的区别是角色和报文格式,而不是哪一方使用哪种密码学。
为什么 HTTPS 代理帮不上忙
终止 TLS 的代理期待的是 TLS 记录流,以及其内部的某种应用协议。 这个协议的控制通道是在自己的帧结构里承载的,外面还包着一层可靠性,所以终止型代理会把这层帧当作损坏的输入。
在不解释内容的前提下转发字节或数据报的透明转发器可以工作,前提是它不重写、不重组这条流。对任何中间设备都该问的问题是:它是否转发真实的传输和帧结构,而不是它是否在某处支持 TLS。在花一晚上调代理规则之前,先问这个问题。这里的失败大多是架构性的,而不是缺了某项设置。
正确读取抓包
预期先出现控制交换,然后出现数量多得多的数据消息,两者共用同一条传输。如果控制阶段完成而数据阶段卡住,故障就与密钥、路由或数据路径的过滤有关,而不是握手。抓几条数据消息,看看它们到底有没有离开客户端。仅凭这一个观察,就能把排查范围缩小一半。
关于密钥方法的历史描述出现在协议文档 中,并不意味着某个版本仍然支持所有旧格式。在依据描述过去做法的段落建立预期之前,先确认你实际运行的版本。