本文主要介绍 iOS 端 App 集成 HTTPDNS 时实现“IP 直连”的解决方案。关于 iOS 上如何集成 HTTPDNS,请先查看 iOS SDK 开发手册 。
1. 前言
在移动端网络环境中,DNS 劫持、LocalDNS 缓存污染等问题时常导致域名无法正确解析、网络请求失败。针对这些场景,阿里云 HTTPDNS 提供了可靠的域名递归解析服务,帮助移动 App 绕过本地 DNS 的潜在风险,提升网络请求的成功率和稳定性。
但是,在 iOS 平台上使用 HTTPDNS,需要将请求原本的域名替换为实际解析出来的 IP 再直接发起请求,这就可能引发额外的问题,尤其是在 HTTPS 和 SNI 等复杂场景下。因此,在集成 HTTPDNS 之前,需要对可能出现的问题和可行的解决方案有一个全面的认识,以便在业务中安全且正确地使用 HTTPDNS。
本文将介绍在 iOS 上使用 HTTPDNS 会遇到的主要问题,并给出在不同场景下的集成方案和各自的利弊,希望帮助开发者快速完成 HTTPDNS 的接入。
2. 在 iOS 上使用 HTTPDNS 会遇到哪些问题
在移动端应用中,如果我们将原始
URL
中的域名(如
example.com
)替换为
HTTPDNS
解析得到的
IP,往往会遇到以下几个方面的问题。之所以会产生这些问题,与
HTTPS
协议在不同层次(TLS/SSL
层和
HTTP
层)对
Host
字段的使用方式密切相关:
-
TLS/SSL 层: 在 HTTPS 场景下,客户端会先进行 TLS/SSL 握手,其中会使用到 URL 中的 Host 来完成以下任务:
-
证书校验 :验证服务器证书的域名(Common Name 或 Subject Alternative Name)是否与请求的 Host 一致。
-
SNI(Server Name Indication) :在建立 TLS 连接时,客户端会将所请求的域名信息(即 URL 中的 Host)发送给服务器,以便服务器返回对应的证书。
-
-
HTTP 层: 在完成 TLS 握手后,客户端会在 HTTP 请求的 Header 中包含
Host字段,用于告诉服务器此请求对应的具体站点或资源。如果将 URL 中的域名替换为 IP,又未手动设置 HTTP Header 里的Host,就可能让服务器无法识别要访问的实际域名,从而导致请求失败或返回异常内容。
根据上述 Host 在 HTTPS 协议栈各层的作用描述,可以看到,若只把 URL 中的域名直接替换为 HTTPDNS 解析出来的 IP,则会引起以下技术问题:
-
域名与证书不匹配 对于 HTTPS 请求,如果 URL 里直接使用 HTTPDNS 解析出来的 IP 作为请求的 Host,TLS 层面无法匹配到正确的证书域名(Common Name 或 SAN 扩展域名),会导致 SSL 握手失败。
-
SNI(Server Name Indication)问题 在 SNI 场景中,同一服务器 IP 可能对应多个域名的证书。如果客户端在 SSL 握手阶段没有传递正确的域名信息(只发送了 IP),服务端就无法返回匹配该域名的证书,导致 SSL 握手失败。由于 iOS 的高层网络 API(如
NSURLSession)并未暴露直接配置 SNI 的接口,SNI 问题常难以通过简单方式解决。 -
Host 头与业务寻址 如果仅把 URL 中的域名替换为 IP,却忘记在 HTTP 请求头中显式设置
Host为原始域名,那么在 HTTP 层服务器端可能无法识别具体的站点或资源。例如 CDN 场景下,服务器需要依赖 Host 字段来分发正确的内容,一旦 Host 为 IP,将导致服务异常。 -
底层网络库的选择 iOS 自带的高层 API(
NSURLSession等)在定制 SNI 或手动证书校验方面扩展性较弱。如果开发者想自行处理 SNI 或修改 TLS 握手逻辑,就需要使用更底层的接口(如CFNetwork、libcurl等),但这会带来更高的开发和维护成本。
综上所述, 直接将 URL 中的域名替换为 HTTPDNS 解析出来的 IP,在 HTTPS 场景下会影响 TLS 层的证书校验与 SNI 传递,并在 HTTP 层可能导致 Host 头信息异常 。因此在 iOS 端整合 HTTPDNS,需要有针对性地解决这些问题,才能确保网络请求的可靠性与安全性。
3. 普通 HTTP 场景、HTTPS+非 SNI 场景接入方案
在针对
普通
HTTP
或
HTTPS + 非
SNI
这两种场景下,我们通常可以继续使用系统自带的
NSURLSession
以及常规的网络请求逻辑,只需做一些相对简单的处理即可。需要注意的是,
普通
HTTP
场景并不涉及
TLS
握手和证书校验;而
HTTPS + 非
SNI
场景需要考虑到证书校验,但可以通过在
NSURLSession
中
Hook
验证流程的方式来应对。
3.1 普通 HTTP 场景标题
对于 普通 HTTP 请求,网络链路中不存在 TLS/SSL 握手,也无需证书校验,因此集成 HTTPDNS 的核心操作仅在 HTTP 层进行:
-
替换请求 URL 中的 Host 为 HTTPDNS 解析出的 IP
-
例如原始请求 URL 为
http://example.com/api,HTTPDNS 解析后得到 IP1.2.3.4,则把 URL 修改为http://1.2.3.4/api。
-
-
在 HTTP Header 中显式设置
Host为原始域名-
若使用
NSMutableURLRequest,可在请求头中添加request.allHTTPHeaderFields[@"Host"] = @"example.com";确保服务器在应用层能够识别到正确的域名。
-
优点 :实现简单;只需在现有 HTTP 请求中替换 Host+设置 Header,开发量较低。
缺点 :仅适用于 HTTP 明文协议,无法解决 HTTPS 场景下的证书校验和 SNI 相关问题。
3.2 HTTPS + 非 SNI 场景标题
对于不使用
SNI
机制或仅包含少量固定域名的
HTTPS
站点(证书中只涵盖这些域名),可以在
NSURLSession
层通过以下方式来完成
HTTPDNS
接入与证书校验:
-
替换请求 URL 中的 Host 为 HTTPDNS 解析出的 IP
-
例如原始请求 URL 为
https://example.com/api,HTTPDNS 解析后得到 IP1.2.3.4,则把 URL 修改为https://1.2.3.4/api。
-
-
在 HTTP Header 中显式设置
Host为原始域名-
同理,可以在
NSMutableURLRequest里设置request.allHTTPHeaderFields[@"Host"] = @"example.com";。
-
-
Hook 证书校验过程
-
由于这是 HTTPS 请求,需要在 TLS 握手阶段进行证书校验。此时,若直接拿 IP 作为 Host 检查,就会出现 域名与证书不匹配 的问题。
-
可以在
NSURLSessionDelegate的回调方法URLSession:didReceiveChallenge:completionHandler:中,将系统获取到的serverTrust进行验证时,改用原始域名(example.com)替换掉 IP,从而通过证书校验。 -
代码示例:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSString *originalHost = [self getOriginalHostFromRequest:task.originalRequest]; SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; if ([self evaluateServerTrust:serverTrust forDomain:originalHost]) { // 证书校验通过 NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } else { // 证书校验失败,使用默认处理 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { // 创建证书校验策略 NSMutableArray *policies = [NSMutableArray array]; if (domain) { [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)]; } else { [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()]; // 绑定校验策略到服务端的证书上 SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies); // 评估当前serverTrust是否可信任,官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html // 关于SecTrustResultType的详细信息请参考SecTrust.h SecTrustResultType result; SecTrustEvaluate(serverTrust, &result); return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); }
-
优点 :
-
不需要引入额外的第三方库,直接使用系统原生
NSURLSession和证书校验逻辑. -
实现成本相对可控,适合不涉及 SNI 或只需少量域名证书的场景。
缺点 :
-
无法处理 SNI 场景。若在同一 IP 上部署多个域名(如 CDN 场景),则仍然会因服务器返回错误的证书而握手失败。
4. HTTPS+SNI 场景接入方案
针对
SNI(单
IP
多
HTTPS
域名)的场景,简单的
NSURLSession
方案中无法在
SSL
握手阶段发送正确的域名信息,导致握手失败。为了解决此问题,需要在更底层的
Socket
层级修改或指定
SNI
字段。常见的做法有以下三种:
4.1 自定义 NSURLProtocol 实现
iOS
允许开发者通过继承
NSURLProtocol
来拦截系统发起的网络请求,并在底层自行实现
HTTP/HTTPS
请求逻辑。可以基于
CFNetwork
或者
NSInputStream/NSOutputStream
等接口,手动完成所有网络操作:
-
拦截请求
-
在
canInitWithRequest:方法中判断是否要拦截当前请求。 -
在
startLoading方法里,将原始请求 URL 中的域名替换为 IP,并保留原始域名用于后续的证书校验和 SNI 设置。
-
-
设置 SNI
-
通过
CFStream相关 API 或者SecureTransport接口,指定kCFStreamSSLPeerName为原始域名,这样在 SSL 握手阶段,底层会带上正确的域名信息。
-
-
证书校验
-
手动执行证书验证流程,确保证书中包含的域名与原始域名匹配。
-
优点 :不依赖第三方库,完全基于系统底层 API,灵活度高。
缺点 :实现成本较高,需要开发者手动处理重定向、Cookie、缓存、编码、流量统计等;不支持连接复用,性能一般;且维护风险大,升级系统或网络环境时需要额外适配。
如果需要参考示例,阿里云提供了
httpdns_ios_demo
中
HttpDnsNSURLProtocolImpl.m
的示例实现,可根据业务需求进行修改或复用。
4.2 自行使用 libcurl 实现网络请求
libcurl
是
C
语言实现的跨平台网络库,支持手动设置
SNI
字段,从而在
SSL
握手阶段传递正确的域名信息,以完成多域名共享同一
IP
的证书校验场景。大致流程如下:
-
解析域名,得到对应 IP
-
例如使用 HTTPDNS 提供的 API
resolveHostSyncNonBlocking:等方法,获取到目标域名的 IP 地址。
-
-
设置 SNI 和 IP 映射
-
通过
CURLOPT_RESOLVE或其他 API,把“域名:端口:解析到的 IP”映射写入到 curl 内部 DNS 缓存。 -
依然使用原始域名作为
CURLOPT_URL的访问目标,保证 TLS 握手时会带上正确的域名信息。
-
-
证书校验
-
libcurl默认开启证书校验,也可以根据需求使用相应的回调对证书进行更细粒度的检查。
-
下面是一个核心示例代码片段(伪代码),演示如何在 iOS 中通过 libcurl 使用 HTTPDNS 解析的结果并完成请求:
CURL *curl_handle = curl_easy_init();
if (curl_handle) {
// 例如从HTTPDNS得到IP = 1.2.3.4,目标域名 = example.com,端口 = 443
struct curl_slist *dnsResolve = NULL;
dnsResolve = curl_slist_append(dnsResolve, "example.com:443:1.2.3.4");
// 设置域名-IP映射
curl_easy_setopt(curl_handle, CURLOPT_RESOLVE, dnsResolve);
// 依然使用原始域名作为URL
curl_easy_setopt(curl_handle, CURLOPT_URL, "https://example.com");
// 开启SSL验证
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2L);
// 发起请求
CURLcode res = curl_easy_perform(curl_handle);
// 检查结果
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
// 清理
curl_easy_cleanup(curl_handle);
curl_slist_free_all(dnsResolve);
}
优点 :
-
成熟、稳定,支持丰富的协议,能适应复杂的网络环境;对 SNI 场景有内置支持。
缺点 :
-
需要将
libcurl编译进 iOS 项目,使用纯 C 接口,对 Objective-C/Swift 项目的开发者有一定学习成本。 -
同时也要处理自定义的请求流程或自行封装 HTTP 逻辑(如 Cookie、重定向、缓存等)。
4.3 使用 EMASCurl
为降低在
iOS
上直接使用
libcurl
的接入门槛,阿里云
EMAS
团队提供了
EMASCurl
库,它对
libcurl
进行封装,并支持与
HTTPDNS
的直接对接。
-
安装与拦截
-
提供了两种主要使用方式: 1)拦截指定
NSURLSessionConfiguration创建的NSURLSession; 2)拦截系统全局[NSURLSession sharedSession]。 -
API 接口请查询 github 上的 README 文件。
-
-
与 HTTPDNS 配合
-
实现
EMASCurlProtocolDNSResolver协议即可将 HTTPDNS 解析结果交给 EMASCurl。 -
在
resolveDomain:方法中调用[HttpDnsService resolveHostSyncNonBlocking:]获取 IP 地址,然后返回给 EMASCurl 来完成后续的 SNI 设置和请求发送。
-
-
证书校验
-
EMASCurl 依赖
libcurl的证书校验机制,开发者也可通过相应接口扩展或自定义证书校验,以适配业务需求。
-
优点:
-
封装了
libcurl的底层能力,API 更符合 iOS 开发者的使用习惯。 -
通过 DNS Hook 机制与 HTTPDNS 轻松对接。
-
同时解决了 SNI 场景的域名传递和证书校验问题,降低集成成本。
缺点 :
-
依赖第三方库(EMASCurl+libcurl),需要注意兼容性、版本升级等。
-
对于非常复杂的 HTTP 特性或自定义需求,仍需要阅读和理解 EMASCurl 内部封装实现,以确保业务可用性。
-
在接入 EMASCurl 时,需要测试常见的 HTTP/HTTPS 特性(如重定向、Cookie、并发请求等)是否符合业务需求。
-
若业务对安全或网络性能有严格要求,需要评估 EMASCurl 在当前 iOS 系统版本下的表现。
-
确保在不同网络环境(Wi-Fi/蜂窝网络/代理等)下都能正常完成请求和握手。
5. 总结
根据业务中是否需要支持 SNI、多域名及证书校验等需求,可结合以下方案进行选择。下表对各方案进行对比:
|
方案 |
适用场景 |
优点 |
缺点 |
|
仅设置 Host 和 Header |
普通 HTTP 场景 |
- 集成成本最低 |
-仅适用于 HTTP 明文协议 |
|
NSURLSession + Hook 证书校验 (仍需设置 Host 和 Header) |
HTTPS+非 SNI 场景 |
- 集成成本低 - 沿用系统 API,无需额外库 |
- 不支持 SNI |
|
自定义 NSURLProtocol |
全部场景 需要更灵活的底层控制 |
- 完全基于系统底层 API - 自由度高 |
- 开发、维护成本高 - 无连接复用,性能一般 - 需要自行处理重定向、Cookie、缓存、编码等特殊场景 |
|
libcurl |
全部场景 跨平台或自定义 HTTP 流程 |
- 成熟、稳定 - 可设置 SNI 字段、丰富协议支持 - 证书校验可灵活扩展 |
- C 接口对 ObjC/Swift 开发者有一定学习成本 - 需要自行封装 Cookie、重定向、缓存等 |
|
EMASCurl |
全部场景 期望 iOS 端简单集成 |
- 对 libcurl 封装较好 - 与 HTTPDNS 对接简单 - 实现 SNI 与证书校验 |
- 依赖第三方库,需要注意兼容与升级 - 特殊需求需阅读源码进行二次开发 |
开发者应结合自身业务的 多域名需求 、 网络安全要求 、 兼容性 、 维护成本 以及 对第三方库的接受度 进行综合评估,选择合适的接入方案,并在上线前充分测试网络请求的可用性和安全性。