为了提升 App 网络请求的稳定可靠,从不同维度考虑有很多的优化方案,今天我们就从“域名解析”切入来讲一讲。
下面先来介绍一下 HTTPDNS 服务,以及接入 HTTPDNS 后对 App 中原有的 HTTPS 请求的证书校验带来的影响和相关解决方案。
HTTPDNS
我们知道,客户端向服务端发起一个请求时,在建立 TCP/IP 连接前,需要有一个步骤就是根据请求 URL 中的域名获取对应服务器的 IP 地址,即 DNS 解析。
但在移动互联网络中,我们经常会遭遇到运营商的 DNS 劫持(利益使然),导致 Web 页面出现弹窗、小广告、服务不稳定、不可用等。为了解决这种情况,很多云服务厂商都提供了 HTTPDNS 服务:
HTTPDNS 使用 HTTP 协议进行域名解析,代替现有基于 UDP 的 DNS 协议,域名解析请求直接发送到云服务商的 HTTPDNS 服务器,从而绕过运营商的 Local DNS,能够有效避免 Local DNS 造成的域名劫持、调度不精准、解析延迟、失败率高、不稳定等问题。 —— 引自阿里云文档
HTTPDNS 的基本原理如下图所示:

问题
当客户端使用 HTTPDNS 解析域名时,请求 URL 中的 host 会被替换成 HTTPDNS 解析出来的 IP,这种方案对于 HTTP 请求不会有任何影响,但是对于 HTTPS 来说,由于请求前多了一个 SSL/TLS 握手过程,涉及到证书校验,这时候问题就来了!
在 SSL/TLS 握手过程中,服务端下发的证书里的 CN 字段(即证书颁发的域名)仍然为域名的形式,但是请求中的 host 在请求前已经被我们替换为 IP 了,这时在证书校验时,就会出现 domain 不匹配的情况,导致 SSL/TLS 握手不成功,请求会被取消掉(error code: -999)。
解决方法
因此,我们需要对证书校验的逻辑做一下小改动,在 NSURLSession 的证书校验代理方法(URLSession:didReceiveChallenge:completionHandler:)中,增加一个前置处理:把待验证的 domian 由原本的 IP 转换为其对应的域名,然后再进行下一步操作。具体的代码如下:
1 | - (void)URLSession:(NSURLSession *)session |
其中,evaluateServerTrust:forDomain: 方法的定义如下,可以参考 AFNetworking 中 AFSecurityPolicy 模块的代码。
1 | - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { |
上述解决方法只适用于一台服务器的 IP 只配置了一个默认的域名和 SSL 证书的情况。
详细的 Demo 参见:TestHTTPDNS
SNI 场景
通常情况下,一台服务器往往会配置多个域名来建立不同 Web 站点或提供不同的服务。例如,域名 a.com 和 b.com 都同时解析到同一 IP 1.1.1.1 上,然后服务器根据客户端请求中的 Host 字段来区分,将请求分配给不同的后台服务来处理。
如前面所述,对于 HTTPS 请求前,需要额外进行 SSL/TLS 握手,但是由于服务器配置了多个域名的 SSL 证书,在握手发送证书时,不知道客户端访问的是哪个域名(因为握手是在某一具体请求之前进行的),所以无法根据不同域名发送不同的证书。
SNI(Server Name Indication) 就是为了解决一个服务器使用多个域名和证书的 SSL/TLS 扩展。它的工作原理是:在进行 SSL/TLS 握手之前,先发送要访问站点的域名(Hostname),这样服务器会根据这个域名返回一个合适的证书。目前,大多数操作系统和浏览器以及主流 HTTP 服务器软件都已经很好地支持 SNI 扩展。
但是同样的问题又来了,当我们采用 HTTPDNS 解析域名,如前所述,请求 URL 中的 host 会被替换成解析后的 IP,此时握手前发送的 SNI 字段是 IP,导致服务器最终获取到的”域名”仍然为 IP,无法找到匹配的证书,只能返回默认的证书或者不返回,所以也会出现 SSL/TLS 握手不成功的错误。
而对于这种场景,iOS 上层网络 API NSURLConnection/NSURLSession 都没有提供相关方法进行 SNI 字段的配置,因此需要 Socket 层级的底层网络库,例如 CFNetwork,来实现 IP 直连网络请求适配方案。详细的解决方案可以参考这篇文章:《HTTPS SNI 业务场景“IP 直连”方案说明》。
注:以上关于 SNI 的部分文字参考了阿里云 HTTPDNS iOS SDK 的相关技术文档,在此特别感谢!
总结
本文简要介绍了 App 接入 HTTPDNS 服务后,对于两种不同的服务器配置场景 单 IP 单域名证书 和 单 IP 多域名证书(SNI),如何解决 HTTPS 请求在 SSL/TLS 握手过程的证书校验问题,不足之处,请多多指正。