全部產品
Search
文件中心

HTTPDNS:在iOS用戶端中使用DoH

更新時間:Sep 05, 2025

本文檔介紹如何在iOS用戶端中使用DoH

背景

目前在iOS上接入使用HTTPDNS,普遍做法是,通過引入HTTPDNS SDK,再針對HTTPS認證校正、SNI擴充等問題做對應處理,從而可以在App內按需使用HTTPDNS的解析能力。參考文檔:iOS端Native情境使用HTTPDNS

Apple在iOS 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解析過程。

範例程式碼

建立用於管理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");
    }
}

降級機制

重要
  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與裝置管理 中安裝此描述檔案。