浏览器通过 HTTPS 打开 VPN 主机名毫无问题,而 SSTP 客户端仍然拒绝连接。这两件事并不矛盾。SSTP 把 TLS 当作外层传输,然后在这条连接内部运行自己的请求和 PPP 载荷,因此页面成功加载只能证明一件很窄的事:主机在 443 端口回答了 GET。客户端问的是另一个问题,服务器对它的回答可能很糟。

页面能打开,隧道却建不起来

人们喜欢用浏览器测试,因为它快,随后就把结果当成服务器可达且配置正确的证据。它只是关于地址栏所打开路径的一次 GET 的证明,仅此而已。VPN 会话在同一次 TLS 握手之后才开始,但发出的是 网页永远不会使用的方法和路径。所以浏览器拿到 200 并不能说明 SSTP 端点是否存在。

把这两个观察分开,排查范围就缩小了。传输和证书链是通的,而它们之上的协议交换某处不通。这跟端口坏了或证书过期是不同的问题,需要不同的证据。

SSTP 客户端实际发送什么

客户端用 SSTP_DUPLEX_POST 方法向 /sra_{GUID}/ 路径发送 HTTP 请求,随后把连接主体保持为承载 PPP 帧的双向流。符合规范的服务器会回答 HTTP/1.1 200,并不再像 Web 服务器那样行事。其他任何情况都是特征:405 或 404 表示端点不存在,301 或 302 表示有东西在重定向请求,而立即出现的 FIN 表示代理或服务器关闭了流。

所以目标必须是 SSTP 服务器,而不是临时加上 TLS 证书的 Web 服务器。方法、路径和长时间保持的主体都是协议要求,反向代理无法仅靠配置补上。一个能完美转发普通 HTTPS 的代理,仍可能拒绝它不认识的方法。

代理或 Web 服务器在哪里打断流

中间设备在转发任何内容之前会检查 HTTP 请求行,带有非常规路径的自定义方法可能过不了它的规则。有些设备照常提供普通页面,却拒绝或中断隧道,于是客户端报告的是先建立后死掉的连接,而不是干净的拒绝。另一些设备接受请求,然后施加 PPP 保活撑不过的主体或空闲超时。两者从客户端看完全一样,所以失败的阶段比错误文本更重要。

让网站和 SSTP 共用 443 端口是路由选择,并不是承诺每一跳都以同样方式处理两种会话。如果路径上有负载均衡器、防火墙或 TLS 终结器,在动 VPN 配置之前先把它们列入嫌疑名单。

分别测试各阶段

按顺序执行检查,并在第一个失败处停下,因为每个阶段排除的层次不同。下面的 TLS 探测把证书身份与 SSTP 的任何行为分开。把主机名换成你自己的端点,读取它打印的 subject 和证书链。

openssl s_client -connect vpn.example.com:443 -servername vpn.example.com

然后把剩下的证据对应到各阶段,而不是从客户端报错去猜。下面的表格就是写进工单的检查清单,其中的特征能区分一种失败和下一种。

StageWhat it provesFailure signature
TCP to 443A listener exists and the path is openTimeout or connection refused
TLS with SNIThe certificate matches the name you useName mismatch or untrusted chain
SSTP_DUPLEX_POST exchangeThe endpoint is an SSTP server405, 404, 301 or an early FIN
PPP authenticationThe user credential was acceptedAuth failure after a 200 response
Inner routingA permitted destination is reachableTunnel up, no reply from the target

在工单里记录最后一个成功的阶段,而不是写“无法连接”。MS-SSTP 规范记录了 TLS 握手之后的请求和 PPP 交换,也解释了为什么浏览器测试无法替代其中任何一个。

先身份,后 PPP

TLS 身份和 PPP 认证是先后关系,不是二选一,把它们混为一谈会把人引向错误的服务器设置。客户端先校验服务器证书及其预期用途,然后 PPP 协商自身状态,并用质询-应答方式或 EAP 认证用户。证书问题产生的错误与被拒绝的密码不同,两种错误都不描述对方覆盖的阶段。

把主机名换成地址,或点过证书警告,等于删掉了本可定位失败阶段的检查,而不是修复它。保留警告并读它。当 HTTP 交换从未走到 SSTP 行为时,也不要去重置 PPP 密码,因为凭据从未被索要过。

相关文章