You need to enable JavaScript to run this app.
导航

在 iOS 客户端配置 DoH 地址

最近更新时间2024.02.20 14:59:57

首次发布时间2023.10.08 19:55:55

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

示例项目

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

前提条件

开启 DoH 并获取接入域名

实现方法

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

下面的代码展示了如何通过 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];