API 在同一台手机的 Safari 里能应答,应用的请求却超时。这个差距通常不在隧道上:调用内网服务的应用发出的仍是普通 HTTPS 请求,设备上已有的 VPN 只决定该请求有没有路径。 在应用里再写一条隧道是产品决策,不是针对超时的修复。下文按失败发生的位置分类,利用应用已经具备的错误表面。

从错误入手,而不是隧道

URLSession 会以 NSError 值的形式在 NSURLErrorDomain 中报告传输失败,而这个代码是最廉价的证据。改动任何配置之前,先把该代码连同失败的主机名和任务耗时记进日志。超时、名称失败和信任失败在屏幕上看起来一模一样,却需要三种不同的修复。先分类,再读对应那一节。

任务指标回答下一个问题:连接到底有没有建立起来,这能把从未有过路径的请求和服务器响应过慢的请求区分开。这些都不需要改隧道;需要的是把请求自身的诊断信息打印到你能读到的地方。

名称解析最先失败

像 api.corp.example 这样的短办公名称通常依赖手机并不携带的搜索域,于是应用解析出一个只存在于办公楼内的名字。 NSURLErrorCannotFindHost 就是它的签名。完整主机名必须通过 VPN 所安装的任意解析器来解析,只要搜索域没有保证,应用就应该请求完全限定形式。把名字换成地址、关掉证书检查,只是把失败挪走,并没有修好它。

私有 DNS 记录和为该名称签发的证书才是受支持的一对。其余都是会流到用户手里的绕行方案。

超时是路径问题,不是名称问题

NSURLErrorTimedOut 意味着名称已解析但无人应答,指向路由、数据包策略或服务。当隧道按应用分配时,另一个应用能到达同一台服务器,并不能说明你的构建也行,因为那份映射并不共享。先确认你运行的包在这份分配之内,再确认服务路径对它放行。安装在分配之外的开发构建可能完全位于隧道之外,看起来就像 API 坏了。

浏览器对比能说明设备上某个应用有路径。却永远不能证明你的构建也有,所以两项测试必须分开做。

信任失败关乎证书上的名字

NSURLErrorServerCertificateUntrusted 连同其下的 TLS 错误一起看,说明证书链或身份校验失败。内部 CA 必须在设备上受信任,而为另一个主机名签发的证书,无论怎么重配网络都仍然是错的。Apple 的 App Transport Security 指南 仍然适用于这些加载 URL 的连接,围绕明文 HTTP 服务架隧道并不能让该服务端到端安全。

修正身份或信任锚,并保持例外列表为空。一刀切的任意加载例外会在开发期掩盖失败,之后又把它留给每一位用户。

HTTP 拒绝属于应用,而不属于网络

握手完成后的 401 或 403 说明请求到了、服务拒绝了,所以剩下的问题是账号、令牌受众、作用域或密钥。记录状态码和主机名,绝不要记录承载令牌或响应体。到这一步隧道已经在正常工作,继续改网络只会拖延真正的修复。

这张表就是放在终端旁边的那份分类:应用真正能报出的每种失败占一行。先打印代码再读它,因为你选的那一行决定了你要顺着哪一节排查。

Where it failsWhat the app seesFirst check
name resolutionNSURLErrorCannotFindHost (-1003)resolve the fully qualified name from the device itself
no path, or no answerNSURLErrorTimedOut (-1001)is this bundle inside the per-app mapping, and is the service path allowed
TLS trustNSURLErrorServerCertificateUntrusted (-1202)does the certificate name match the host, and is the internal CA trusted
service refusalHTTP 401 or 403 after a completed handshakeaccount, token audience, scope or key

每一行都比下面一行更省事,每一行都排除同一请求的不同层。沿着表往下走,直到某行对上,然后就不要再改网络了。

相关文章