本文档介绍如何在iOS客户端中使用DoH
背景
目前在iOS上接入使用HTTPDNS,普遍做法是,通过引入HTTPDNS SDK,再针对HTTPS证书校验、SNI扩展等问题做对应处理,从而可以在App内按需使用HTTPDNS的解析能力。参考文档:iOS端Native场景使用HTTPDNS
Apple在iOS 14+引入了安全DNS,新增了一种可行方案,可以通过DoH的方式,接入使用HTTPDNS,下面两个WWDC视频可供参考。
Improve DNS security for apps and servers:介绍了安全DNS相关的背景知识。
Enable encrypted DNS:介绍如何使用安全DNS,包括系统级和App级的两种使用方式。
使用安全DNS的方式,是iOS原生支持,直接在App或者设备全局网络处理上生效,无需在代码层面修改网络请求细节,方案整体更优雅。但与此同时,它也有很大的局限性,假设通过安全DNS的方式接入HTTPDNS,则:
只能在App维度或者设备维度生效,对应范围内的域名解析全部会走HTTPDNS,包括App中三方SDK中的网络请求等,无法做细粒度控制。
如果选择在设备维度内生效,需要终端用户授予特殊权限。一般来说只有网络工具类应用才能申请此权限。
前提条件
开启DoH并获取DoH接入地址,请参考DoH开启与使用。
如果 DoH 没有处于开启状态,解析请求会失败,HTTPDNS 服务端会返回 400 错误码。
如果「域名解析范围」设置为「域名列表中的域名」时,对于没有在域名列表中添加的域名,HTTPDNS 服务端会返回 200 的状态码但没有解析结果。
如果「域名解析范围」设置为「所有域名」时,对于在黑名单的域名,HTTPDNS 服务端会返回 200 的状态码但没有解析结果。
使用应用级的DoH配置
iOS 14+ network.framework提供了privacyContext为应用独立配置DoH,可以控制应用在生命周期内的DNS解析过程。
示例代码
创建用于管理NSURLSession的DataTaskManager
@interface DataTaskManager : NSObject <NSURLSessionTaskDelegate>
#import "DataTaskManager.h"
@import Network;
@import Foundation;
- (instancetype)init {
self = [super init];
if (self) {
_networkQueue = dispatch_queue_create("com.taskmanager.queue", DISPATCH_QUEUE_SERIAL);
[self setupDoHConfiguration];
}
return self;
}
- (void)setupDoHConfiguration {
dispatch_async(self.networkQueue, ^{
NSLog(@"Setting up DoH configuration...");
// Create URL endpoint for HTTPDNS DoH
const char *dohServerURL = "https://1xxxx3.aliyunhttpdns.com/dns-query";
nw_endpoint_t urlEndpoint = nw_endpoint_create_url(dohServerURL);
NSLog(@"Using DoH server: %s", dohServerURL);
nw_resolver_config_t resolverConfig = nw_resolver_config_create_https(urlEndpoint);
nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, true, resolverConfig);
NSLog(@"DoH configuration applied to privacy context");
});
}
推荐在ViewController.viewDidLoad
完成初始化
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
......
self.dataTaskManager = [[DataTaskManager alloc] init];
......
}
URLSession和基于URLSession的三方网络库在进行网络请求时,会使用默认的PrivacyContext实例,当完成DoH配置后,当前App所有的URLSession请求的DNS解析都会使用DoH。
解析性能数据埋点
可以使用NSURLSessionTaskMetrics
对DNS过程进行埋点,查看DoH解析是否生效以及DNS解析耗时。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
NSLog(@"\n=== Collecting metrics for request to: %@ ===\n", task.originalRequest.URL);
task.taskDescription = [NSString stringWithFormat:@"%.2f,%@",
metrics.taskInterval.duration,
task.originalRequest.URL.absoluteString];
if ([metrics.transactionMetrics count] > 0) {
[metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
NSString *fetchTypeStr = @"Unknown";
switch (obj.resourceFetchType) {
case NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad:
fetchTypeStr = @"Network Load";
break;
case NSURLSessionTaskMetricsResourceFetchTypeServerPush:
fetchTypeStr = @"Server Push";
break;
case NSURLSessionTaskMetricsResourceFetchTypeLocalCache:
fetchTypeStr = @"Local Cache";
break;
}
NSLog(@"Fetch Type: %@", fetchTypeStr);
if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
NSString *dnsProtocolStr = @"Unknown (0)";
BOOL isDoH = NO;
switch (dnsProtocol) {
case NSURLSessionTaskMetricsDomainResolutionProtocolUDP:
dnsProtocolStr = @"UDP (1)";
break;
case NSURLSessionTaskMetricsDomainResolutionProtocolTCP:
dnsProtocolStr = @"TCP (2)";
break;
case NSURLSessionTaskMetricsDomainResolutionProtocolTLS:
dnsProtocolStr = @"TLS (3)";
break;
case NSURLSessionTaskMetricsDomainResolutionProtocolHTTPS:
dnsProtocolStr = @"HTTPS/DoH (4)";
isDoH = YES;
break;
}
NSLog(@"DNS Protocol: %@", dnsProtocolStr);
#if TARGET_OS_SIMULATOR
NSLog(@"Running in simulator - DNS protocol detection not supported");
#else
if (!isDoH) {
NSLog(@"DoH not detected");
}
#endif
// 获取DNS解析性能数据
if (obj.domainLookupStartDate && obj.domainLookupEndDate) {
int dnsLookupTime = ceil([obj.domainLookupEndDate timeIntervalSinceDate:obj.domainLookupStartDate] * 1000);
NSLog(@"DNS Lookup Details:");
NSLog(@" Start: %@", obj.domainLookupStartDate);
NSLog(@" End: %@", obj.domainLookupEndDate);
NSLog(@" Duration: %d ms", dnsLookupTime);
} else {
NSLog(@"No DNS lookup performed (might be cached)");
}
// 获取网络请求性能数据
if (obj.connectStartDate && obj.connectEndDate) {
NSTimeInterval connectionTime = [obj.connectEndDate timeIntervalSinceDate:obj.connectStartDate];
NSLog(@"Connection Time: %.3f seconds", connectionTime);
}
}
}];
} else {
NSLog(@"No transaction metrics available");
}
}
降级机制
在完成降级后,会使应用中后续所有基于URLSession的请求生效。
DoH配置可以在iOS 14+真机和模拟器生效,但是在模拟器运行时,通过
NSURLSessionTaskMetrics
读取的NSURLSessionTaskMetricsDomainResolutionProtocol
固定为 0 (Unknown),在模拟器测试相关功能时需要关注这个差异。
配置超时设置,在DNS解析失败时可以及时拿到异常信息:
// 设置连接超时
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.waitsForConnectivity = NO;
config.timeoutIntervalForRequest = 5;
config.timeoutIntervalForResource = 10;
在上文中的埋点链路中,可以制定合适的降级策略,以下是一个识别到DNS异常时,全局降级到LocalDNS的例子:
......
if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
NSString *dnsProtocolStr = @"Unknown (0)";
BOOL isDoH = NO;
switch (dnsProtocol) {
case NSURLSessionTaskMetricsDomainResolutionProtocolUDP:
dnsProtocolStr = @"UDP (1)";
break;
case NSURLSessionTaskMetricsDomainResolutionProtocolTCP:
dnsProtocolStr = @"TCP (2)";
break;
case NSURLSessionTaskMetricsDomainResolutionProtocolTLS:
dnsProtocolStr = @"TLS (3)";
break;
case NSURLSessionTaskMetricsDomainResolutionProtocolHTTPS:
dnsProtocolStr = @"HTTPS/DoH (4)";
isDoH = YES;
break;
}
NSLog(@"DNS Protocol: %@", dnsProtocolStr);
#if TARGET_OS_SIMULATOR
NSLog(@"Running in simulator - DNS protocol detection not supported");
#else
if (!isDoH) {
NSLog(@"DoH not detected, falling back to local DNS");
dispatch_async(dispatch_get_main_queue(), ^{
// 关闭 DoH 并使用 LocalDNS
nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, false, nil);
});
}
#endif
}
......
如果「域名解析范围」设置为「域名列表中的域名」。
对于没有在域名列表中添加的域名,示例中的 dnsProtocolStr 会打印出 "Unknown (0)"。
对于在域名列表中添加的域名,示例中的 dnsProtocolStr 会打印出 "HTTPS/DoH (4)"。
如果「域名解析范围」设置为「所有域名」。
对于在黑名单的域名,示例中的 dnsProtocolStr 会打印出 "Unknown (0)"。
对于不在黑名单中的域名,示例中的 dnsProtocolStr 会打印出 "HTTPS/DoH (4)"。
使用系统级的DoH配置
可以通过配置系统的描述文件为iOS设备配置DoH,注意系统级DoH会影响设备上的所有应用。
参考以下步骤配置DoH地址:
将示例的内容,替换 DoH 地址后,保存为
.mobileconfig
文件,例如,my_company_doh.mobileconfig
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>DNSSettings</key>
<dict>
<key>DNSProtocol</key>
<string>HTTPS</string>
<key>ServerURL</key>
<!---- 把此处的地址替换为 DoH 接入地址 --->
<string>https://1xxxx3.aliyunhttpdns.com/dns-query</string>
</dict>
<key>PayloadDescription</key>
<string>Configures iOS to use EMAS HTTPDNS DoH</string>
<key>PayloadDisplayName</key>
<string>EMAS HTTPDNS DoH</string>
<key>PayloadIdentifier</key>
<string>com.apple.dnsSettings.managed.9B498EC0C-EF6C-44F0-BFB7-0000658B99AC</string>
<key>PayloadType</key>
<string>com.apple.dnsSettings.managed</string>
<key>PayloadUUID</key>
<string>465AB183-5E34-4794-9BEB-B5327CF61F27</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>ProhibitDisablement</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Adds EMAS HTTPDNS DoH configuration to iOS</string>
<key>PayloadDisplayName</key>
<string>EMAS HTTPDNS DoH Configuration</string>
<key>PayloadIdentifier</key>
<string>com.emas.apple-dns</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>130E6D6F-69A2-4515-9D77-99342CB9AE76</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
将
my_company_doh.mobileconfig
发布到文件存储服务器或者通过邮件发送到iOS设备。在iOS设备上通过浏览器或者邮箱客户端下载
my_company_doh.mobileconfig
。在 设置 > 通用 > VPN与设备管理 中安装此描述文件。
- 本页导读 (0)
- 背景
- 前提条件
- 使用应用级的DoH配置
- 示例代码
- 解析性能数据埋点
- 降级机制
- 使用系统级的DoH配置