You need to enable JavaScript to run this app.
导航
在 iOS 客户端配置 DoH 地址
最近更新时间:2024.03.29 17:41:10首次发布时间:2023.10.08 19:55:55

本文档介绍如何在 iOS 客户端配置 DoH 地址。

示例项目

参见 获取并运行 iOS 示例项目 获取包含 DoH 功能的示例项目。

前提条件

开启 DoH 并获取接入域名

警告

如果您希望移动解析 HTTPDNS 使用 DoH 解析某个域名,您必须确保在控制台添加该域名并开启 DoH接入

  • 对于没有在控制台添加的域名,解析会失败,HTTPDNS 服务端会返回 403 错误码。
  • 如果 DoH接入 没有处于开启状态,解析会失败,HTTPDNS 服务端会返回 403 错误码。

实现方法

HTTPDNS iOS SDK 不支持 DoH 协议。您需要通过 iOS 的原生方法接入 DoH。您可以选择以下任意一种方法。

注意

您只能在 iOS 真机上验证 DoH 功能。本文档介绍的配置方法在 iOS 模拟器中不生效。

PrivacyContext

您可以通过 PrivacyContext 为 iOS 设备在 App 级别配置 DoH。
下面的示例代码对单个 PrivacyContext 实例生效。

#import <Network/Network.h>

if (@available(iOS 14.0, *)) {
    // 打开 DoH
    nw_endpoint_t dohEndpoint = 
            nw_endpoint_create_url("https://doh-xxxxxxxxxxxxxxx.volcdns.pub/dns-query");
    nw_resolver_config_t dohResolver = 
            nw_resolver_config_create_https(dohEndpoint);
    nw_privacy_context_require_encrypted_name_resolution(
            NW_DEFAULT_PRIVACY_CONTEXT, true, dohResolver);
    // 关闭 DoH
    nw_privacy_context_require_encrypted_name_resolution(
            NW_DEFAULT_PRIVACY_CONTEXT, false, nil);
}

下面的示例代码对单个 NWConnection 实例生效。

#import <Network/Network.h>

nw_privacy_context_t connection_context = 
        nw_privacy_context_create("connection context");
nw_endpoint_t dohEndpoint = 
        nw_endpoint_create_url("https://doh-xxxxxxxxxxxxxxx.volcdns.pub/dns-query");
nw_resolver_config_t dohResolver = 
        nw_resolver_config_create_https(dohEndpoint);
nw_privacy_context_require_encrypted_name_resolution(
        connection_context, true, dohResolver);

const char *hostname = "imap.gmail.com";
const char *port = "imaps";
nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION, NW_PARAMETERS_DEFAULT_CONFIGURATION);
nw_parameters_set_privacy_context(parameters, connection_context);
nw_endpoint_t endpoint = nw_endpoint_create_host(hostname, port);
nw_connection_t connection = nw_connection_create(endpoint, parameters);

NEDNSSettingsManager

您可以为您的 App 创建一个 Network Extensions,并通过 NEDNSSettingsManager 为 iOS 设备在系统级别配置 DoH。

说明

您的 Xcode 项目的 Provisioning Profile 必须开启 Network Extensions 中的 DNS Settings。
图片

下面的代码展示了如何通过 NEDNSSettingsManager 在系统级别配置 DoH。

let manager : NEDNSSettingsManager = NEDNSSettingsManager.shared()

    @objc func updateSettings(disableCell: Bool, disableWiFi: Bool, completionHandler: @escaping() -> Void) {
        manager.loadFromPreferences { error in
            if error != nil {
                print("load failed")
                return
            }
            // onDemandRules settings
            // ...
            // 保存 DNS 配置,如果之前已经安装过,那么会提示因为相同配置而保存失败。
            let doh = NEDNSOverHTTPSSettings()
            doh.serverURL = URL(string: "https://doh-xxxxxxxxxxxxxxx.volcdns.pub/dns-query")
            self.manager.dnsSettings = doh
            self.manager.saveToPreferences { saveError in
                if saveError != nil {
                    print("save failed")
                    print("saveError is %@", saveError!)
                    return
                } else {
                    print("save success")
                }
            }
        }
    }

代码运行后,DNS 配置会被安装到 iOS 系统。为了让 DNS 配置生效,用户需要在 iOS 设备的 设置 > 通用 > VPN 与设备管理 > DNS 中手动启用 DNS 配置。
生效范围是系统级别,但是您可以通过 onDemandRules 控制域名和网络环境等生效范围。

配置描述文件

您可以通过配置描述文件为 iOS 设备在系统级别配置 DoH。
下面的 XML 代码展示了一个配置描述的文件的示例。您需要把示例中的 DoH 接入域名替换为您从控制台获取的 DoH 接入域名。

<?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://doh-xxxxxxxxxxxxxxx.volcdns.pub/dns-query</string>
            <!----把此处的域名替换为您的 DoH 接入域名--->         
         </dict>
         <key>PayloadDescription</key>
         <string>Configures device to use Volcengine DoH</string>
         <key>PayloadDisplayName</key>
         <string>DoH</string>
         <key>PayloadIdentifier</key>
         <string>com.apple.dnsSettings.managed.C498EC0C-EF6C-44F0-BFB7-0000658B99AC</string>
         <key>PayloadType</key>
         <string>com.apple.dnsSettings.managed</string>
         <key>PayloadUUID</key>
         <string>065AB183-5E34-4794-9BEB-B5327CF61F27</string>
         <key>PayloadVersion</key>
         <integer>1</integer>
         <key>ProhibitDisablement</key>
         <false/>
      </dict>
   </array>
   <key>PayloadDescription</key>
   <string>Adds the Volcengine DoH to iOS</string>
   <key>PayloadDisplayName</key>
   <string>Volcengine DoH</string>
   <key>PayloadIdentifier</key>
   <string>com.volcengine.apple-dns</string>
   <key>PayloadRemovalDisallowed</key>
   <false/>
   <key>PayloadType</key>
   <string>Configuration</string>
   <key>PayloadUUID</key>
   <string>030E6D6F-69A2-4515-9D77-99342CB9AE76</string>
   <key>PayloadVersion</key>
   <integer>1</integer>
</dict>
</plist>

把内容保存为一个 .mobileconfig 文件,例如 volcengine-https.mobileconfig,并且在您的 iOS 设备上安装该文件。参见 在 iPhone 或 iPad 上安装配置描述文件 了解安装方法。

开发注意事项

数据埋点和回退机制

PrivacyContext 为例,您可以:

  • 通过 URLSessionDelegate 获取 NSURLSessionTaskTransactionMetrics,进一步获取 DNS 类型和 DNS 耗时。
  • 通过 PrivacyContext 开启 DoH,在请求失败后关闭 DoH。
#pragma mark URLSession Delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
   
    if ([metrics.transactionMetrics count] > 0) {
        [metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
            if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
                
                NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
                NSLog(@"DNS类型为 %ld", (long)dnsProtocol);
                NSLog(@"未知,UDP,TCP,TLS,HTTPS,4为 DoH");
                if (dnsProtocol == NSURLSessionTaskMetricsDomainResolutionProtocolUnknown) {
                    NSLog(@"dns来源未知,关闭 DoH");
                    nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, false, nil);
                }
                
                if (obj.domainLookupStartDate && obj.domainLookupEndDate) {
                    int dnsLookupTime = ceil([obj.domainLookupEndDate timeIntervalSinceDate:obj.domainLookupStartDate] * 1000);
                    NSLog(@"DNS开始时间:%@,DNS结束时间:%@", obj.domainLookupStartDate, obj.domainLookupEndDate);
                    NSLog(@"DNS解析时长 单位ms:%d",dnsLookupTime);
                }
            }
        }];
    }
}

超时控制

通过分阶段超时快速失败来控制 DoH 失效超时问题劣化,DoH 失败后,关闭 DoH。

NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.timeoutIntervalForRequest = 5;
config.timeoutIntervalForResource = 10;
NSURLSession* session = [NSURLSession sessionWithConfiguration: config delegate:self delegateQueue:nil];