WWDC Quick Look 💓 By SwiftGGTeam
Enable encrypted DNS

Enable encrypted DNS

观看原视频

Highlight

Apple 平台原生支持 DNS over TLS 和 DNS over HTTPS,开发者可以用 NEDNSSettingsManager 发布系统级 DNS 配置,也可以用 Network.framework 的 PrivacyContext 为 App 连接指定加密 DNS。

核心内容

App 访问网站时,系统先要把域名解析成服务器地址。这个问题通常会发给本地网络配置的 DNS 服务器。逐字稿里把风险说得很直接:DNS 问答通常走未加密的 UDP,同一网络里的其他设备能看到你查了哪些名字,还可能干扰返回结果。

另一个问题来自本地解析器本身。加入公共 Wi-Fi 后,你的互联网使用情况可能被跟踪或阻断。加密 DNS 的作用,就是用加密保护 DNS 问答;当你不信任当前网络时,还能把查询发给你信任的 DNS 服务器。

从 2020 年开始,Apple 平台原生支持两种协议:DNS over TLS(DoT)和 DNS over HTTPS(DoH)。二者都用 TLS 加密 DNS 消息,DoH 还使用 HTTP 来改善性能。

这场 session 给了两条接入路径。第一条是系统级:DNS 服务提供者可以写一个 NetworkExtension App,或由企业通过 MDM 下发 DNSSettings payload,把单个加密 DNS 服务器配置成系统默认解析器。第二条是 App 级:开发者可以在自己的 App 里,为部分连接或全部连接启用加密 DNS。

系统级配置还要处理网络兼容性。企业 Wi-Fi 里的私有域名只能由企业 DNS 服务器解析;VPN 和 captive network 又有自己的解析规则。Session 的重点不只是打开加密,还包括用 Network Rules 指定什么时候启用、什么时候例外。

详细内容

1. 创建系统级 DNS 配置

04:16

系统 DNS 设置可以由使用 NEDNSSettingsManager 的 NetworkExtension App 配置,也可以由包含 DNSSettings payload 的 MDM profile 配置。两种方式描述同一类内容:DNS 服务器配置,以及决定配置何时生效的 Network Rules。

下面是 session 里的 DoH 配置片段:

// 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
        }
    }
}

关键点:

  • loadFromPreferences 先读取已有配置,避免直接覆盖用户当前状态。
  • NEDNSOverHTTPSSettings 定义一个 DoH 服务器;服务器 IP 地址是可选项。
  • serverURL 是 DoH 配置的必填项。
  • 把配置赋给 dnsSettings 后,调用 saveToPreferences 应用到系统。
  • 保存后,用户还需要在 Settings App 里启用这个 DNS 服务器。

2. 用 Network Rules 处理私有网络

05:1206:40

公共 DNS 服务器解析不了只存在于本地网络的私有名字。Session 举的例子是企业 Wi-Fi:员工访问的某些私有域名,只有企业网络里的 DNS 服务器知道。

Apple 已经自动处理了两类兼容性场景。登录咖啡店网络这类 captive network 会得到例外;VPN 激活时,VPN tunnel 内的解析会使用 VPN 的 DNS 设置。企业私有域名这类规则,需要 App 自己配置 Network Rules。

// 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
]

关键点:

  • NEOnDemandRuleEvaluateConnection 让规则只匹配指定网络类型和 SSID。
  • NEEvaluateConnectionRuleenterprise.example.net 从加密 DNS 设置中排除。
  • NEOnDemandRuleDisconnect 在蜂窝网络上关闭这组 DNS 设置。
  • NEOnDemandRuleConnect 是兜底规则,让 DNS 设置默认开启。
  • onDemandRules 是有序列表,前面的规则先匹配。

3. 在 App 连接里启用加密 DNS

10:47

如果你没有提供整个系统都能使用的 DNS 服务,也可以只在自己的 App 里启用加密 DNS。Session 明确提到,这条路径适用于 URLSessionTask、Network framework 连接,以及 getaddrinfo 这类 POSIX API。

Network framework 的入口是 PrivacyContext。每一组共享 DNS 设置的连接使用一个 PrivacyContext。当你要求加密解析时,可以传入一个 fallback DNS 服务器配置;如果系统已经有 DNS 配置,系统级配置优先,否则 App 的 fallback 生效。

// 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)

关键点:

  • NWParameters.PrivacyContext 保存一组 DNS 隐私设置。
  • requireEncryptedNameResolution(true, fallbackResolver:) 要求连接使用加密名称解析。
  • .https(url, serverAddresses:) 指向 DoH 服务器和它的地址。
  • setPrivacyContext 把 DNS 设置挂到 NWParameters.tls 上。
  • 使用这组参数创建的 NWConnection 启动后,会按这组设置解析主机名。

4. 验证连接实际使用的 DNS 协议

11:3512:07

连接 ready 后,可以请求 EstablishmentReport。报告里有一组 resolution steps,每一步会记录 DNS 协议:HTTPS、TLS、UDP 或 TCP。Transcript 也提醒,来自缓存的答案可能没有协议字段。

如果你要让整个 App 都采用同一套设置,可以配置默认 PrivacyContext。这会影响 App 发起的每一次 DNS 解析,包括 URLSessionTaskgetaddrinfo

// 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(...)

关键点:

  • NWParameters.PrivacyContext.default 是 App 级默认 DNS 隐私设置。
  • 设置默认 context 后,URLSession 发起的解析会使用这组配置。
  • 低层 API 里的 getaddrinfo 也会受这组配置影响。
  • 如果网络按策略阻止加密 DNS,系统会给 Wi-Fi 标记 privacy warning,App 连接会失败,而不是改用会泄露隐私的解析路径。

核心启发

  • 做一个 DNS 服务商配置 App:如果你提供公共 DNS 服务,可以用 NEDNSSettingsManager 写一个只负责发布 DoH 或 DoT 配置的 App;入口是 NEDNSOverHTTPSSettings 或对应的 DoT 设置,保存后让用户到 Settings App 中启用。

  • 给企业网络做规则模板:把用户常用的企业 Wi-Fi SSID、私有域名和蜂窝网络策略做成界面选项;底层写入 NEOnDemandRuleEvaluateConnectionNEEvaluateConnectionRuleNEOnDemandRuleDisconnect 和兜底的 NEOnDemandRuleConnect

  • 给敏感连接加一层 App 内 DNS 策略:对登录、支付、同步这类连接创建专用 NWParameters.PrivacyContext;用 requireEncryptedNameResolution(true, fallbackResolver:) 指向可信 DoH 服务器,再把 context 放进 NWParameters.tls

  • 做一个连接诊断页面:在连接 ready 后请求 EstablishmentReport,读取每次解析的 dnsProtocol;页面只展示 HTTPS、TLS、UDP、TCP 这类协议状态,帮助定位设备是否真的走了加密 DNS。

  • 把默认 PrivacyContext 接进旧网络层:如果项目里同时有 URLSessiongetaddrinfo,可以先配置 NWParameters.PrivacyContext.default,让 App 内 DNS 解析统一使用同一套 fallback 设置。

关联 Session

评论

GitHub Issues · utterances