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 处理私有网络
公共 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。NEEvaluateConnectionRule把enterprise.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 协议
连接 ready 后,可以请求 EstablishmentReport。报告里有一组 resolution steps,每一步会记录 DNS 协议:HTTPS、TLS、UDP 或 TCP。Transcript 也提醒,来自缓存的答案可能没有协议字段。
如果你要让整个 App 都采用同一套设置,可以配置默认 PrivacyContext。这会影响 App 发起的每一次 DNS 解析,包括 URLSessionTask 和 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(...)
关键点:
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、私有域名和蜂窝网络策略做成界面选项;底层写入
NEOnDemandRuleEvaluateConnection、NEEvaluateConnectionRule、NEOnDemandRuleDisconnect和兜底的NEOnDemandRuleConnect。 -
给敏感连接加一层 App 内 DNS 策略:对登录、支付、同步这类连接创建专用
NWParameters.PrivacyContext;用requireEncryptedNameResolution(true, fallbackResolver:)指向可信 DoH 服务器,再把 context 放进NWParameters.tls。 -
做一个连接诊断页面:在连接 ready 后请求
EstablishmentReport,读取每次解析的dnsProtocol;页面只展示 HTTPS、TLS、UDP、TCP 这类协议状态,帮助定位设备是否真的走了加密 DNS。 -
把默认 PrivacyContext 接进旧网络层:如果项目里同时有
URLSession和getaddrinfo,可以先配置NWParameters.PrivacyContext.default,让 App 内 DNS 解析统一使用同一套 fallback 设置。
关联 Session
- Boost performance and security with modern networking:同一主题下的现代网络 API 总览,覆盖 IPv6、HTTP/2、TLS 1.3 和 encrypted DNS。
- Support local network privacy in your app:解释 iOS 14 本地网络权限,和本篇一样关注网络访问中的隐私透明度。
- Build local push connectivity for restricted networks:讲受限网络里的本地推送连接,适合和 NetworkExtension 相关配置一起看。
- Build trust through better privacy:从平台隐私功能角度解释如何建立用户信任,可作为本篇加密 DNS 的上层背景。
评论
GitHub Issues · utterances