This document describes how to use DoH in iOS clients
Background
The common way to integrate HTTPDNS on iOS is to import the HTTPDNS software development kit (SDK). Then, you can handle issues such as HTTPS certificate verification and Server Name Indication (SNI) extension. This lets you use the HTTPDNS resolution feature in your app as needed. For more information, see Use HTTPDNS in native iOS scenarios.
Apple introduced secure DNS in iOS 14+, adding a new feasible solution to integrate HTTPDNS through DoH. The following two WWDC videos can serve as references.
Improve DNS security for apps and servers: Introduces the background knowledge of secure DNS.
Enable encrypted DNS: Introduces how to use secure DNS, including both system-level and App-level implementation methods.
Using secure DNS is natively supported by iOS, taking effect directly at the App or device global network processing level without requiring modifications to network request details at the code level, making the solution more elegant overall. However, it also has significant limitations. If HTTPDNS is integrated through secure DNS:
It can only take effect at the App level or device level, and all domain name resolutions within the corresponding scope will go through HTTPDNS, including network requests from third-party SDKs in the App, without the ability to control at a fine-grained level.
If you choose to make it effective at the device level, end users need to grant special permissions. Generally, only network utility applications can apply for this permission.
Prerequisites
Enable DoH and obtain the DoH ingest endpoint. For more information, see Configure the DoH service.
If DoH is not enabled, resolution requests will fail, and the HTTPDNS server will return a 400 error code.
If you set Domain Resolution Scope to Domains in the domain list, the HTTPDNS server returns a 200 status code but no resolution result for domains that are not in the list.
If "Domain Resolution Scope" is set to "All domains", for domains in the blacklist, the HTTPDNS server will return a 200 status code but no resolution results.
Using application-level DoH configuration
iOS 14+ network.framework provides privacyContext to configure DoH independently for applications, which can control the DNS resolution process during the application lifecycle.
Sample code
Create a DataTaskManager to manage NSURLSession.
@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");
});
}Complete the initialization in ViewController.viewDidLoad.
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
......
self.dataTaskManager = [[DataTaskManager alloc] init];
......
}URLSession and third-party network libraries based on URLSession will use the default PrivacyContext instance when making network requests. When the DoH configuration is completed, all URLSession requests in the current App will use DoH for DNS resolution.
Resolution performance instrumentation
You can use NSURLSessionTaskMetrics to instrument the DNS process. This lets you check if DoH resolution is working and monitor the DNS resolution time.
- (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
// Get DNS resolution performance data
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)");
}
// Get network request performance data
if (obj.connectStartDate && obj.connectEndDate) {
NSTimeInterval connectionTime = [obj.connectEndDate timeIntervalSinceDate:obj.connectStartDate];
NSLog(@"Connection Time: %.3f seconds", connectionTime);
}
}
}];
} else {
NSLog(@"No transaction metrics available");
}
}Fallback mechanism
After completing the fallback, it will affect all subsequent URLSession-based requests in the application.
The DoH configuration works on real iOS 14 and later devices and in emulators. However, when you run the app in an emulator, the
NSURLSessionTaskMetricsDomainResolutionProtocolthat is read byNSURLSessionTaskMetricsis always 0 (Unknown). Note this difference when you test related features in an emulator.
Configure timeout settings to obtain exception information promptly when DNS resolution fails:
// Set connection timeout
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.waitsForConnectivity = NO;
config.timeoutIntervalForRequest = 5;
config.timeoutIntervalForResource = 10;In the instrumentation chain mentioned above, you can formulate an appropriate fallback strategy. Here is an example of globally falling back to LocalDNS when a DNS exception is detected:
......
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(), ^{
// Disable DoH and use LocalDNS
nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, false, nil);
});
}
#endif
}
......If "Domain Resolution Scope" is set to "Domains in the domain list".
For domains not in the domain list, the dnsProtocolStr in the example prints "Unknown (0)".
For domains in the domain list, the dnsProtocolStr in the example prints "HTTPS/DoH (4)".
If "Domain Resolution Scope" is set to "All domains".
For domains in the blacklist, the dnsProtocolStr in the example will print "Unknown (0)".
For domains not in the blacklist, the dnsProtocolStr in the example will print "HTTPS/DoH (4)".
Using system-level DoH configuration
You can configure DoH for iOS devices by configuring system profiles. Note that system-level DoH will affect all applications on the device.
Follow these steps to configure the DoH address:
Replace the DoH address in the sample content and save the content as a
.mobileconfigfile, such asmy_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>
<!---- Replace the address here with the DoH access address --->
<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>
Publish
my_company_doh.mobileconfigto a file storage server or send it to the iOS device by email.On the iOS device, download
my_company_doh.mobileconfigusing a browser or an email client.Install this profile in Settings > General > VPN & Device Management.