行业新闻与博客

Android 中的 DNS-over-HTTP/3 的安全性您了解多少?

为了帮助保护 Android 用户的 DNS 查询的私密性,Android 支持加密的 DNS。除了对 DNS-over-TLS 的现有支持之外,Android 现在还支持 DNS-over-HTTP/3,它比 DNS-over-TLS 有许多改进。

大多数网络连接都是从 DNS 查找开始的。虽然传输安全可能应用于连接本身,但 DNS 查找传统上默认不是私有的:基本 DNS 协议是未加密的原始 UDP。随着时间的推移,互联网已经迁移到 TLS,但 DNS 存在引导问题。证书校验依赖对方域,要么需要 DNS 本身,要么把问题移到 DHCP(可能被恶意控制)。Google、Cloudflare、OpenDNS 和 Quad9 等中央解析器缓解了这个问题,它们允许设备为每个网络在本地配置单个 DNS 解析器,覆盖通过 DHCP 提供的内容。

在 Android 9.0 中,我们 宣布了 私有 DNS 功能,该功能 在服务器启用和支持时使用 DNS-over-TLS (DoT) 来保护 DNS 查询。不幸的是,DoT 会为每个 DNS 请求产生开销。另一种加密 DNS 协议 DNS-over-HTTPS (DoH) 在业内迅速获得关注,因为 DoH 已经被大多数公共 DNS 运营商部署,包括 Cloudflare Resolver 和 Google Public DNS。虽然单独使用 HTTPS 不会显着减少开销,但 HTTP/3 使用 QUIC,一种使用具有会话恢复功能的单个 TLS 会话通过 UDP 有效地多路复用多个流的传输。所有这些功能对于在移动设备上高效运行至关重要。

DNS-over-HTTP/3 (DoH3) 支持作为 Google Play 系统更新的 一部分发布 ,因此当您阅读本文时,Android 11 及更高版本的 Android 设备1将使用 DoH3 而不是众所周知的 DoT 2 支持它的 DNS 服务器。您正在使用的 DNS 服务不受此更改的影响;只有运输将升级。将来,我们的目标是支持 DDR,这将使我们能够为任何服务器动态选择正确的配置。此功能应该会降低加密 DNS 对性能的影响。

表现

DNS-over-HTTP/3 避免了 DNS-over-TLS 操作可能出现的几个问题:

  • 由于 DoT 在单个请求和响应流上运行,因此 许多 服务器实现都受到 队头阻塞3 的影响。这意味着,如果行首的请求需要一段时间才能解决(可能是因为需要递归解决),否则本应快速解决的后续请求的响应将被阻塞,等待第一个请求。相比之下,DoH3 在单独的 逻辑流上运行每个请求,这意味着默认情况下实现将无序地解析请求。
  • 移动设备随着用户四处移动而频繁地改变网络。使用 DoT,这些事件需要对连接进行完全重新协商。相比之下,QUIC 传输 HTTP/3 基于可以在单个 RTT 中恢复挂起的连接。
  • DoT 打算让许多查询在开始时使用相同的连接来分摊 TCP 和 TLS 握手的成本。不幸的是,在实践中有几个因素(例如网络断开或服务器 TCP 连接管理)使这些连接的寿命不如我们希望的那么长。一旦连接关闭,再次建立连接至少需要 1 个 RTT。

    在不可靠的网络中,DoH3 甚至可能胜过传统的 DNS。虽然不直观,但这是因为 QUIC 中的流量控制机制可以提醒任何一方未收到数据包。在传统的 DNS 中,查询的超时需要基于整个查询的预期时间,而不仅仅是解析器接收数据包的时间。



在此功能最初有限推出期间的现场测量表明,DoH3 显着提高了 DoT 的性能。对于成功的查询,我们的研究表明,用 DoH3 替换 DoT 可以将中值查询时间减少 24%,将第 95 个百分位数的查询时间减少 44%。虽然似乎怀疑报告的数据是以成功查询为条件的,但 DoT 和 DoH3 都成功解决了 97% 的查询,因此它们的指标可以直接比较。UDP 仅成功解析了 83% 的查询。因此,UDP 延迟不能直接与 TLS/HTTP3 延迟相比较,因为非面向连接的协议对什么是“查询”有不同的概念。我们仍然将其包含在内以进行粗略比较。

内存安全

DNS 解析器处理可能被攻击者控制的输入,包括来自网络和设备上的应用程序。为了降低安全漏洞的风险,我们选择使用内存安全语言来实现。

幸运的是,我们一直在 为 Android 平台添加 Rust 支持。这项工作正是针对这样的情况——需要高性能或低级别(在本例中都是)的系统级功能,并且在 C++ 中实现这些功能会带来风险。虽然我们之前已经推出了 Keystore 2.0,但这代表了我们首次涉足主线模块中的 Rust。Cloudflare 维护着一个名为 quiche 的 HTTP/3 库,它非常适合我们的用例,因为它具有内存安全的实现、很少的依赖项和较小的代码大小。Quiche 还 支持直接从 C++ 使用. 我们考虑过这一点,但即使是请求调度服务也有足够的复杂性,我们也选择在 Rust 中实现该部分。

我们使用 Tokio 异步框架 构建了查询引擎, 以同时处理新请求、传入数据包事件、控制信号和计时器。在 C++ 中,这可能需要多个线程或精心设计的事件循环。通过利用 Rust 中的异步,这发生在具有最少锁定 4的单个线程上。DoH3 实现有 1,640 行,并使用单个运行时线程。相比之下,DoT 需要 1,680 行,同时管理更少,并且每个使用中的 DoT 服务器最多使用 4 个线程。

安全和性能——终于在一起

随着 Rust 的引入,我们能够同时提高安全性和性能。同样,QUIC 允许我们同时提高网络性能和隐私。最后,Mainline 确保此类改进能够更快地惠及更多 Android 用户。