在iOS客户端中使用DoH

更新时间:2025-04-18 02:53:47

本文档介绍如何在iOS客户端中使用DoH

背景

目前在iOS上接入使用HTTPDNS,普遍做法是,通过引入HTTPDNS SDK,再针对HTTPS证书校验、SNI扩展等问题做对应处理,从而可以在App内按需使用HTTPDNS的解析能力。参考文档:iOSNative场景使用HTTPDNS

AppleiOS 14+引入了安全DNS,新增了一种可行方案,可以通过DoH的方式,接入使用HTTPDNS,下面两个WWDC视频可供参考。

重要

使用安全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解析过程。

示例代码

创建用于管理NSURLSessionDataTaskManager

@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。

解析性能数据埋点

可以使用NSURLSessionTaskMetricsDNS过程进行埋点,查看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");
    }
}

降级机制

重要
  1. 在完成降级后,会使应用中后续所有基于URLSession的请求生效。

  2. DoH配置可以在iOS 14+真机和模拟器生效,但是在模拟器运行时,通过NSURLSessionTaskMetrics读取的NSURLSessionTaskMetricsDomainResolutionProtocol固定为 0 (Unknown),在模拟器测试相关功能时需要关注这个差异。

  1. 配置超时设置,在DNS解析失败时可以及时拿到异常信息:

// 设置连接超时
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.waitsForConnectivity = NO;
config.timeoutIntervalForRequest = 5;
config.timeoutIntervalForResource = 10;
  1. 在上文中的埋点链路中,可以制定合适的降级策略,以下是一个识别到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地址:

  1. 将示例的内容,替换 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>
  1. my_company_doh.mobileconfig发布到文件存储服务器或者通过邮件发送到iOS设备。

  2. iOS设备上通过浏览器或者邮箱客户端下载my_company_doh.mobileconfig

  3. 设置 > 通用 > VPN与设备管理 中安装此描述文件。

  • 本页导读 (0)
  • 背景
  • 前提条件
  • 使用应用级的DoH配置
  • 示例代码
  • 解析性能数据埋点
  • 降级机制
  • 使用系统级的DoH配置
AI助理

点击开启售前

在线咨询服务

你好,我是AI助理

可以解答问题、推荐解决方案等