WWDC Quick Look 💓 By SwiftGGTeam
Enable encrypted DNS

Enable encrypted DNS

Watch original video

Highlight

The Apple platform natively supports DNS over TLS and DNS over HTTPS. Developers can use NEDNSSettingsManager to publish system-level DNS configuration, or use Network.framework’s PrivacyContext to specify encrypted DNS for App connections.

Core Content

When an app accesses a website, the system first resolves the domain name into a server address. This problem is usually addressed to the DNS server configured on the local network. The risk is stated very directly in the verbatim: DNS questions and answers usually use unencrypted UDP. Other devices on the same network can see which names you have checked, and may also interfere with the returned results.

Another problem comes from the local parser itself. When you join a public Wi-Fi, your internet usage may be tracked or blocked. The function of encrypted DNS is to use encryption to protect DNS questions and answers; when you do not trust the current network, you can also send the query to a DNS server you trust.

Starting in 2020, Apple platforms natively support two protocols: DNS over TLS (DoT) and DNS over HTTPS (DoH). Both use TLS to encrypt DNS messages, and DoH also uses HTTP to improve performance.

This session gives two access paths. The first is the system level: the DNS service provider can write a NetworkExtension App, or the enterprise can issue the DNSSettings payload through MDM to configure a single encrypted DNS server as the system default resolver. The second one is at the app level: developers can enable encrypted DNS for some or all connections in their apps.

System-level configuration also deals with network compatibility. Private domain names in corporate Wi-Fi can only be resolved by corporate DNS servers; VPNs and captive networks have their own resolution rules. The focus of Session is not just to turn on encryption, but also to use Network Rules to specify when to enable and when to make exceptions.

Detailed Content

1. Create system-level DNS configuration

(04:16)

System DNS settings can be configured by the NetworkExtension App using NEDNSSettingsManager, or by an MDM profile containing the DNSSettings payload. Two ways of describing the same thing: DNS server configuration, and Network Rules that determine when the configuration takes effect.

The following is a DoH configuration fragment in the session:

// Create a DNS configuration

import NetworkExtension

NEDNSSettingsManager.shared().loadFromPreferences { loadError in
    if let loadError = loadError {
        // ...handle error...
        return
    }
    let dohSettings = NEDNSOverHTTPSSettings(servers: [ "2001:db8::2" ])
    dohSettings.serverURL = URL(string: "https://dnsserver.example.net/dns-query")
    NEDNSSettingsManager.shared().dnsSettings = dohSettings
    NEDNSSettingsManager.shared().saveToPreferences { saveError in
        if let saveError = saveError {
            // ...handle error...
            return
        }
    }
}

Key points:

  • loadFromPreferences reads the existing configuration first to avoid directly overwriting the user’s current status.
  • NEDNSOverHTTPSSettings defines a DoH server; the server IP address is optional.
  • serverURL is required for DoH configuration.
  • After assigning the configuration to dnsSettings, call saveToPreferences to apply it to the system.
  • After saving, the user also needs to enable this DNS server in the Settings App.

2. Use Network Rules to handle private networks

(05:12, 06:40)

Public DNS servers cannot resolve private names that exist only on the local network. The example given by Session is corporate Wi-Fi: some private domain names accessed by employees are only known by the DNS server in the corporate network.

Apple already handles both types of compatibility scenarios automatically. Exceptions will be made when logging into captive networks such as coffee shop networks; when the VPN is activated, the resolution within the VPN tunnel will use the VPN’s DNS settings. Rules such as enterprise private domain names require the App to configure Network Rules itself.

// Apply network rules

let workWiFi = NEOnDemandRuleEvaluateConnection()
workWiFi.interfaceTypeMatch = .wiFi
workWiFi.ssidMatch = ["MyWorkWiFi"]
workWiFi.connectionRules =
    [ NEEvaluateConnectionRule(matchDomains: ["enterprise.example.net"],
                               andAction: .neverConnect) ]

let disableOnCell = NEOnDemandRuleDisconnect()
disableOnCell.interfaceTypeMatch = .cellular

let enableByDefault = NEOnDemandRuleConnect()

NEDNSSettingsManager.shared().onDemandRules = [
    workWiFi,
    disableOnCell,
    enableByDefault
]

Key points:

  • NEOnDemandRuleEvaluateConnection makes the rule match only the specified network type and SSID.
  • NEEvaluateConnectionRule excludes enterprise.example.net from encrypted DNS settings.
  • NEOnDemandRuleDisconnect turns off this set of DNS settings on cellular networks.
  • NEOnDemandRuleConnect is a catch-all rule that enables DNS settings to be enabled by default.
  • onDemandRules is an ordered list, the previous rules are matched first.

3. Enable encrypted DNS in App connection

(10:47)

If you do not provide a DNS service that can be used by the entire system, you can also enable encrypted DNS only in your app. Session explicitly mentions that this path applies to URLSessionTask, Network framework connections, and POSIX APIs such as getaddrinfo.

The entrance to the Network framework is PrivacyContext. One PrivacyContext is used for each set of connections that share DNS settings. When you require encrypted resolution, you can pass in a fallback DNS server configuration; if the system already has a DNS configuration, the system-level configuration takes precedence, otherwise the App’s fallback takes effect.

// Use encrypted DNS with NWConnection

import Network

let privacyContext = NWParameters.PrivacyContext(description: "EncryptedDNS")
if let url = URL(string: "https://dnsserver.example.net/dns-query") {
    let address = NWEndpoint.hostPort(host: "2001:db8::2", port: 443)
    privacyContext.requireEncryptedNameResolution(true,
        fallbackResolver: .https(url, serverAddresses: [ address ]))
}

let tlsParams = NWParameters.tls
tlsParams.setPrivacyContext(privacyContext)

let conn = NWConnection(host: "www.example.com", port: 443, using: tlsParams)
conn.start(queue: .main)

Key points:

  • NWParameters.PrivacyContext holds a set of DNS privacy settings.
  • requireEncryptedNameResolution(true, fallbackResolver:) requires the connection to use encrypted name resolution.
  • .https(url, serverAddresses:) points to the DoH server and its addresses.
  • setPrivacyContext hooks DNS settings to NWParameters.tls.
  • After the NWConnection created with this set of parameters is started, the host name will be resolved according to this set of settings.

4. Verify the actual DNS protocol used by the connection

(11:35, 12:07)

Once the connection is ready, you can request an EstablishmentReport. The report has a set of resolution steps, each of which records the DNS protocol: HTTPS, TLS, UDP or TCP. Transcript also warns that answers from cache may not have protocol fields.

If you want the entire app to use the same set of settings, you can configure the default PrivacyContext. This affects every DNS resolution initiated by the app, including URLSessionTask and getaddrinfo.

// Use encrypted DNS for other APIs

import Network

if let url = URL(string: "https://dnsserver.example.net/dns-query") {
    let address = NWEndpoint.hostPort(host: "2001:db8::2", port: 443)
    NWParameters.PrivacyContext.default.requireEncryptedNameResolution(true,
        fallbackResolver: .https(url, serverAddresses: [ address ]))
}

let task = URLSession.shared.dataTask(with: ...)
task.resume()

getaddrinfo(...)

Key points:

  • NWParameters.PrivacyContext.default is the app-level default DNS privacy setting.
  • After setting the default context, parsing initiated by URLSession will use this set of configurations.
  • getaddrinfo in the low-level API will also be affected by this set of configurations.
  • If the network blocks encrypted DNS by policy, Wi-Fi will be marked with a privacy warning and the app connection will fail instead of using a resolution path that compromises privacy.

Core Takeaways

  • Make a DNS service provider configuration App: If you provide public DNS services, you can use NEDNSSettingsManager to write an App that is only responsible for publishing DoH or DoT configurations; the entrance is NEDNSOverHTTPSSettings or the corresponding DoT settings. After saving, let the user activate it in the Settings App.

  • Make rule templates for enterprise networks: Make user-commonly used enterprise Wi-Fi SSIDs, private domain names, and cellular network policies into interface options; write NEOnDemandRuleEvaluateConnection, NEEvaluateConnectionRule, NEOnDemandRuleDisconnect and the hidden NEOnDemandRuleConnect at the bottom layer.

  • Add a layer of in-app DNS policy to sensitive connections: Create dedicated NWParameters.PrivacyContext for connections such as login, payment, and synchronization; use requireEncryptedNameResolution(true, fallbackResolver:) to point to the trusted DoH server, and then put the context into NWParameters.tls.

  • Make a connection diagnosis page: Request EstablishmentReport after the connection is ready, and read the dnsProtocol parsed each time; the page only displays the status of protocols such as HTTPS, TLS, UDP, and TCP to help locate whether the device has really encrypted DNS.

  • Connect the default PrivacyContext to the old network layer: If there are both URLSession and getaddrinfo in the project, you can configure NWParameters.PrivacyContext.default first so that the same set of fallback settings can be used for DNS resolution in the app.

Comments

GitHub Issues · utterances