为了提升 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 握手过程的证书校验问题,不足之处,请多多指正。