This document describes how to integrate the native encrypted DNS solution in iOS 14.
Overview
DNS resolution is the first hop in accessing network resources. Starting with iOS 14, the system natively supports two standard protocols for encrypted DNS: DNS over TLS (DoT) and DNS over HTTPS (DoH). These protocols address the following two issues:
1. Traditional local DNS queries and responses use unencrypted UDP, which can lead to problems like DNS hijacking.
2. The local DNS server itself may be untrustworthy, or the local DNS service may be unavailable.
To address issues in the DNS resolution process, HTTPDNS provides a solution that uses the HTTPDNS SDK. However, using the SDK introduces challenges, such as handling direct IP connections in 302 redirect scenarios and SNI issues on iOS. The Encrypted DNS feature in iOS 14 effectively solves these problems related to SDK integration. You can refer to the following example to learn how to set HTTPDNS as the default encrypted DNS resolver.
-
The native encrypted DNS solution for iOS 14 is available only on physical devices running iOS 14 or later.
-
To ensure your app runs correctly, implement a fallback mechanism to local DNS. This prevents domain resolution failures if the DoH or DoT service becomes unavailable.
-
Unlike SDK integration, the native solution does not offer automatic service failover and is not covered by a Service-Level Agreement (SLA). If you use the native solution, you must implement your own service exception monitoring and automatic fallback to the device's local DNS.
How to integrate the native encrypted DNS solution of iOS 14 with HTTPDNS
iOS 14 provides two methods for setting up encrypted DNS.
-
The first method enables encrypted DNS for a single app.
To use encrypted DNS only for your app, use nw_privacy_context_t from the NetworkExtension framework. This ensures all DNS resolutions from your app use the encrypted configuration. For an example, refer to our demo that enables DoH for all connections of a single app.
The following code shows how to use the DoH protocol to enable encrypted DNS for a single app:
#import <NetworkExtension/NetworkExtension.h>
if (@available(iOS 14.0, *)){
nw_privacy_context_t defaultPrivacyContext = NW_DEFAULT_PRIVACY_CONTEXT;
nw_endpoint_t dohResolverEndpoint = nw_endpoint_create_url("https://*****-************.alidns.com/dns-query"); //DoH encrypted address
nw_endpoint_t v4ResolverEndpoint1 = nw_endpoint_create_host("223.5.5.5", "443");
nw_endpoint_t v4ResolverEndpoint2 = nw_endpoint_create_host("223.6.6.6", "443");
nw_endpoint_t v6ResolverEndpoint1 = nw_endpoint_create_host("2400:3200::1", "443");
nw_endpoint_t v6ResolverEndpoint2 = nw_endpoint_create_host("2400:3200:baba::1", "443");
nw_resolver_config_t fallbackResolvers = nw_resolver_config_create_https(dohResolverEndpoint);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint2);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint2);
nw_privacy_context_require_encrypted_name_resolution(defaultPrivacyContext, true, fallbackResolvers);
}
The following code shows how to use the DoT protocol to enable encrypted DNS for a single app:
#import <NetworkExtension/NetworkExtension.h>
if (@available(iOS 14.0, *)){
nw_privacy_context_t defaultPrivacyContext = NW_DEFAULT_PRIVACY_CONTEXT;
nw_endpoint_t dotResolverEndpoint = nw_endpoint_create_host("******-************.alidns.com", "853"); //DoT encrypted address
nw_endpoint_t v4ResolverEndpoint1 = nw_endpoint_create_host("223.5.5.5", "853");
nw_endpoint_t v4ResolverEndpoint2 = nw_endpoint_create_host("223.6.6.6", "853");
nw_endpoint_t v6ResolverEndpoint1 = nw_endpoint_create_host("2400:3200::1", "853");
nw_endpoint_t v6ResolverEndpoint2 = nw_endpoint_create_host("2400:3200:baba::1", "853");
nw_resolver_config_t fallbackResolvers = nw_resolver_config_create_tls(dotResolverEndpoint);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint2);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint2);
nw_privacy_context_require_encrypted_name_resolution(defaultPrivacyContext, true, fallbackResolvers);
}
The following code implements a fallback to local DNS. It uses URLSessionDelegate to retrieve NSURLSessionTaskTransactionMetrics and disables DoH/DoT after a request fails.
#pragma mark URLSession Delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
if ([metrics.transactionMetrics count] <= 0) return;
[metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
if (@available(iOS 14.0, *)) {
NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
NSLog(@"%@",[NSString stringWithFormat:@"DNS type is %ld", (long)dnsProtocol]);
NSLog(@"%@",[NSString stringWithFormat:@"0:Unknown, 1:UDP, 2:TCP, 3:TLS, 4:HTTPS"]);
if (dnsProtocol == NSURLSessionTaskMetricsDomainResolutionProtocolUnknown) {
NSLog(@"%@",[NSString stringWithFormat:@"DNS source unknown, disabling DoH"]);
nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, false, nil);
}
}
}
}];
}
-
The second method enables encrypted DNS system-wide.
To use encrypted DNS for the entire system, create a NetworkExtension app that uses the NEDNSSettingsManager API to configure system-wide encrypted DNS.
The following code shows how to configure system-wide DNS by using NetworkExtension with the DoH protocol:
import NetworkExtension
NEDNSSettingsManager.shared().loadFromPreferences { loadError in
if let loadError = loadError {
// ...handle error...
return
}
let dohSettings = NEDNSOverHTTPSSettings(servers: ["223.5.5.5","223.6.6.6","2400:3200:baba::1","2400:3200::1"])
dohSettings.serverURL = URL(string: "https://*****-************.alidns.com/dns-query") //DoH encrypted address
NEDNSSettingsManager.shared().dnsSettings = dohSettings
NEDNSSettingsManager.shared().saveToPreferences { saveError in
if let saveError = saveError {
// ...handle error...
return
}
}
}
The following code shows how to configure system-wide DNS by using NetworkExtension with the DoT protocol:
import NetworkExtension
NEDNSSettingsManager.shared().loadFromPreferences { loadError in
if let loadError = loadError {
// ...handle error...
return
}
let dotSettings = NEDNSOverTLSSettings(servers: ["223.5.5.5","223.6.6.6","2400:3200:baba::1","2400:3200::1"])
dotSettings.serverName = "******-************.alidns.com" //DoT encrypted address
NEDNSSettingsManager.shared().dnsSettings = dotSettings
NEDNSSettingsManager.shared().saveToPreferences { saveError in
if let saveError = saveError {
// ...handle error...
return
}
}
}
A DNS configuration includes HTTPDNS server addresses, DoT/DoH protocols, and a set of network rules. Network rules ensure that the DNS settings are compatible with different networks.
The following code is an example of how to set network rules:
let workWiFi = NEOnDemandRuleEvaluateConnection()
workWiFi.interfaceTypeMatch = .wiFi
workWiFi.ssidMatch = ["MyWorkWiFi"]
workWiFi.connectionRules = [NEEvaluateConnectionRule(matchDomains: ["enterprise.example"], andAction: .neverConnect)]
let disableOnCell = NEOnDemandRuleDisconnect()
disableOnCell.interfaceTypeMatch = .cellular
let enableByDefault = NEOnDemandRuleConnect()
NEDNSSettingsManager.shared().onDemandRules = [
workWiFi,
disableOnCell,
enableByDefault
]
The preceding code sets three network rules. The first rule applies the DNS configuration on the Wi-Fi network with the SSID "MyWorkWiFi", but excludes the private enterprise domain enterprise.example. The second rule disables the configuration on a cellular network. The third rule, NEOnDemandRuleConnect, enables the DNS configuration by default. Because the DNS configuration is handled by the system, your NetworkExtension app does not need a separate process. You only need to enable the DNS Settings capability in your project's Network Extension settings.
Run the NetworkExtension app to install the DNS configuration on the device. You must then manually activate it in Settings > General > VPN & Network > DNS.
On the DNS page, your installed configuration (for example, DoHTestDemo) appears as an option. Select it instead of the default Automatic setting to enable encrypted DNS.