在多数解释里,关于 SSTP 的两个事实被混在一起,而正是这种混合让抓包变得难懂。应用数据包在受保护的通道内继续存在,而承载该通道的 TCP 连接自始至终保留自己的地址。线路上跑的是承载者加上它的货物,任何一方都不可被替代。从外向内走一遍协议栈,每越过一道边界就标记一次。

从外层承载者开始

线路上首先出现的是一条通往 443 端口的普通 TCP 连接,带有路由器真正转发的地址。正是这条连接挺过了网络地址转换,也是接入服务商能够统计的对象。保护载荷并不移除承载者,而且 它也不会隐藏流量大小或时间。

此后的所有内容都是被承载,而不是被路由。即使内部交互失败,承载者仍在继续工作。

沿封装向里走

受保护通道内依次是 SSTP 控制交换、PPP 会话,然后是客户端希望送达的网络数据包。每一层都有自己的协商、自己的错误消息和自己的状态机。因此同一个故障从一个角度看像 TLS 故障,从另一个角度看却像 PPP 故障。按顺序读各层,就能知道是哪台状态机停住了。

一次抓包很少能看到完整协议栈。外层头部只有在端点之外才可见,内层头部则要等解封装之后。这种不对称来自你所站的位置,而不是协议本身。

下图把各层分组,只是为了能讨论它们。它不是字节布局,不承诺每个数据包一条记录,也没有说明某种实现会执行多少次密码运算。

outer IP header
  TCP to port 443
    TLS record layer
      SSTP control and data packets
        PPP frames
          inner IP packet
            application transport and payload

读抓包之前先标记观察点

同一次会话的两份抓包回答的是不同问题,在哪块接口上抓的决定了能回答哪些。在接入路径上,你能看到承载者和 TLS 记录,并能在看不到内层地址的情况下测量大小与间隔。在端点处,解封装之后,内层数据包就是私有转发策略下的普通流量。

解释之前,先在每份抓包上写明接口和方向。从物理网卡抓到的轨迹永远不会把内层应用的地址显示为普通外层字段,把这种缺失读成加密失败是常见错误。从隧道内部抓到的轨迹根本不显示承载者,因此回答不了关于服务商视角的问题。同样的纪律也适用于端点日志,它记录的是协商顺序,而不是内容。

保留图无法证明的那些检查

层的顺序并不能证明某次部署校验了服务器证书、协商了受支持的 TLS 版本或对账号做了授权。这些各自都是与封装无关、可能单独失败的配置决定。

用会报告自身协商结果的客户端去验证,而不是从示意图推理。先从 TLS 握手开始,然后是 SSTP 交换,然后是 PPP,在第一个报错的阶段停下。第一条命令打印证书链与协商版本,第二条列出抓包中出现的握手。

openssl s_client -connect vpn.corp.internal:443 -servername vpn.corp.internal
tshark -r sstp.pcap -Y "tls.handshake.type == 1"

相关文章