博客 | NGINX

NGINX 中的 QUIC 网络和加密入门

NGINX-F5-horiz-black-type-RGB 的一部分
罗伯特·海恩斯缩略图
罗伯特·海恩斯
2023 年 4 月 19 日发布

NGINX 博客上第一次提到 QUIC 和 HTTP/3 是在四年前(!),和您一样,我们现在也热切期待我们的 QUIC 实现即将合并到 NGINX 开源主线分支中。 考虑到长期的酝酿,如果您没有对 QUIC 进行过多思考,这是可以理解的。

然而,此时,作为开发人员或站点管理员,您需要了解 QUIC 如何将某些网络细节的责任从操作系统转移到 NGINX(以及所有 HTTP 应用程序)。 即使网络不是你的强项,采用 QUIC 也意味着担心网络现在(至少有一点)是你工作的一部分。

在这篇文章中,我们深入探讨了 QUIC 中使用的关键网络和加密概念,为了追求清晰,我们简化了一些细节并省略了非必要信息。 虽然在此过程中可能会丢失一些细微差别,但我们的目的是为您提供足够的信息,以便您在您的环境中有效地采用 QUIC,或者至少为您构建知识奠定基础。

如果您对 QUIC 很陌生,我们建议您先阅读我们之前的一篇文章并观看我们的概述视频。

为了更详细、更完整地解释 QUIC,我们推荐 IETC QUIC 工作组提供的出色的《QUIC 传输协议的可管理性》文档,以及本文档中链接的其他材料。

为什么要关心 QUIC 中的网络和加密?

到目前为止,客户端和 NGINX 之间的网络连接的细节对于大多数用户来说并不是特别重要。 毕竟,有了 HTTP/ 1.x和 HTTP/2,操作系统就会负责在客户端和 NGINX 之间建立传输控制协议 (TCP) 连接。一旦建立连接,NGINX 就会使用该连接。

然而,使用 QUIC,连接创建、验证和管理的责任从底层操作系统转移到了 NGINX。NGINX 现在不再接收已建立的 TCP 连接,而是获得用户数据报协议 (UDP) 数据报流,它必须将其解析为客户端连接和流。 NGINX 现在还负责处理数据包丢失、连接重启和拥塞控制。

此外,QUIC 将连接启动、版本协商和加密密钥交换结合到单个连接建立操作中。 尽管 QUIC+HTTP/3 和 TCP+HTTP/1+2 的 TLS 加密处理方式大致相似,但对于下游设备(如第 4 层负载均衡器、防火墙和安全设备)来说,存在可能显著的差异。

最终,这些变化的总体效果是为用户带来更安全、更快速、更可靠的体验,而对 NGINX 配置或操作的影响却很小。 然而,NGINX 管理员需要至少了解一点 QUIC 和 NGINX 的运行情况,以便在出现问题时尽可能缩短平均恢复时间

(值得注意的是,虽然这篇文章重点介绍 HTTP 操作,因为 HTTP/3 需要 QUIC,但 QUIC 也可以用于其他协议。 一个很好的例子是 QUIC 上的 DNS,如RFC 9250专用 QUIC 连接上的 DNS )中所定义。)

介绍完毕,让我们深入了解一些 QUIC 网络细节。

TCP 与 UDP

QUIC 对用于在客户端和服务器之间传输 HTTP应用数据的底层网络协议进行了重大改变。

如上所述,TCP 一直是传输 HTTP Web应用数据的协议。 TCP 旨在通过 IP 网络可靠地传输数据。 它具有明确定义和易于理解的建立连接和确认数据接收的机制,以及用于管理不可靠和拥塞网络上常见的数据包丢失和延迟的各种算法和技术。

虽然 TCP 提供了可靠的传输,但在性能和延迟方面也存在一些缺点。 此外,数据加密并非内置于 TCP 中,必须单独实现。 面对不断变化的 HTTP 流量模式,改进或扩展 TCP 也很困难——因为 TCP 处理是在 Linux 内核中执行的,所以任何更改都必须经过仔细设计和测试,以避免对整体系统性能和稳定性产生意想不到的影响。

另一个问题是,在许多情况下,客户端和服务器之间的 HTTP 流量会经过多个 TCP 处理设备,例如防火墙或负载均衡器(统称为“中间箱”),这些设备可能无法快速实现 TCP 标准的更改。

QUIC 反而使用 UDP 作为传输协议。 UDP 被设计用于像 TCP 一样通过 IP 网络传输数据,但它有意处理连接建立和可靠传送。 这种缺乏开销的特性使得 UDP 适用于许多效率和速度比可靠性更重要的应用。

然而,对于大多数网络应用来说,可靠的数据传输至关重要。 由于底层 UDP 传输层不提供可靠的数据传输,这些功能需要由 QUIC(或应用本身)提供。 幸运的是,QUIC 在这方面比 TCP 有几个优势:

  • QUIC 处理在 Linux 用户空间中执行,其中特定操作的问题对整个系统的风险较小。 这使得快速开发新功能变得更加可行。
  • 上面提到的“中间件”通常对 UDP 流量进行最少的处理,因此不会限制对 QUIC 协议的增强。

简化的 QUIC 网络结构

QUIC是包含 HTTP/3 请求或响应(或任何其他应用数据)的逻辑对象。 对于网络端点之间的传输,它们被包裹在多个逻辑层内,如图所示。

图 1. QUIC 流的剖析

从外到内,逻辑层和对象是:

  • UDP 数据报——包含指定源端口和目标端口的报头(以及长度和校验和数据),后跟一个或多个 QUIC 数据包。 数据报是通过网络从客户端传输到服务器的信息单位。
  • QUIC 数据包– 包含一个 QUIC 标头和一个或多个 QUIC 帧。
  • QUIC 标头——包含有关数据包的元数据。 标头有两种类型:

    • 长标头,在建立连接时使用。
    • 短标头,在连接建立后使用。 它包含(除其他数据外)连接 ID、数据包号和密钥阶段(用于跟踪哪些密钥用于加密数据包,以支持密钥轮换)。 对于特定的连接和密钥阶段,数据包编号是唯一的(并且始终增加)。
  • — 包含类型、流 ID、偏移量和流数据。 流数据分布在多个帧中,但可以使用连接 ID、流 ID 和偏移量进行组装,以正确的顺序呈现数据块。
  • – 单个 QUIC 连接内的单向或双向数据流。 每个 QUIC 连接可以支持多个独立流,每个流都有自己的流 ID。如果包含一些流的 QUIC 数据包丢失,这不会影响丢失数据包中未包含的任何流的进度(这对于避免 HTTP/2 遇到的队头阻塞至关重要)。 流可以是双向的并可由任一端点创建。

建立连接

熟悉的SYN / SYN-ACK / ACK三次握手建立 TCP 连接:

该图显示了客户端和服务器在握手过程中交换的三条消息,以建立 TCP 连接
图 2. 建立 TCP 连接的三次握手

建立 QUIC 连接涉及类似的步骤,但效率更高。 它还将地址验证作为加密握手的一部分构建到连接设置中。 地址验证可以防御流量放大攻击,在这种攻击中,不良行为者会向服务器发送一个包含伪造的源地址信息的数据包,发送给目标攻击受害者。 攻击者希望服务器向受害者生成比攻击者自身能够生成的更多或更大的数据包,从而导致大量的流量。 (有关更多详细信息,请参阅 RFC 9000第 8 节QUIC: 基于 UDP 的多路复用和安全传输。)

作为建立连接的一部分,客户端和服务器提供独立的连接 ID,这些 ID 编码在 QUIC 标头中,提供连接的简单标识,独立于客户端源 IP 地址。

但是,由于 QUIC 连接的初始建立还包括交换 TLS 加密密钥的操作,因此对于服务器来说,其计算成本比在建立 TCP 连接期间生成的简单SYN-ACK响应更高。 它还为分布式拒绝服务 (DDoS)攻击创建了一个潜在的载体,因为在进行密钥交换操作之前未验证客户端 IP 地址。

但是,您可以通过将quic_retry指令设置为on来配置 NGINX,以在复杂的加密操作开始之前验证客户端 IP 地址。 在这种情况下,NGINX 向客户端发送一个包含令牌的重试数据包,客户端必须将其包含在连接设置数据包中。

图表显示了建立 QUIC 连接的握手过程(有和没有重放数据包)
图 3. QUIC 连接建立(有和没有重试数据包)

这种机制有点像三次 TCP 握手,并且至关重要的是,它确定客户端拥有其所呈现的源 IP 地址。 如果没有这项检查,像 NGINX 这样的 QUIC 服务器可能会容易受到带有伪造源 IP 地址的 DoS 攻击。 (减轻此类攻击的另一种 QUIC 机制是要求所有初始连接数据包必须填充到至少 1200 字节,这使得发送它们成为一项更昂贵的操作。)

此外,重试数据包通过将连接的详细信息编码在发送给客户端的连接 ID 中,可以减轻类似于 TCP SYN洪水攻击的攻击(服务器资源被存储在内存中的大量已打开但未完成的握手所耗尽);这还有一个好处,即不需要保留任何服务器端信息,因为可以根据客户端随后提供的连接 ID 和令牌重建连接信息。 该技术类似于 TCP SYN cookie。 此外,NGINX 等 QUIC 服务器可以提供一个过期令牌,供客户端未来的连接使用,以加快连接恢复速度。

使用连接 ID 可以使连接独立于底层传输层,因此网络变化不会导致连接中断。 这在“优雅地管理客户端 IP 地址变化”中进行了讨论。

损失检测

建立连接后(并启用加密,如下所述),HTTP 请求和响应可以在客户端和 NGINX 之间来回流动。UDP 数据报被发送和接收。 然而,有许多因素可能会导致其中一些数据报丢失或延迟。

TCP 具有复杂的机制来确认数据包传送、检测数据包丢失或延迟、以及管理丢失数据包的重新传输,从而向应用层传送正确排序的完整数据。 UDP 缺乏此功能,因此拥塞控制和丢失检测在 QUIC 层实现。

  • 客户端和服务器都会对收到的每个 QUIC 数据包发送明确的确认(尽管仅包含低优先级帧的数据包不会立即得到确认)。
  • 如果包含需要可靠传送的帧的数据包在设定的超时期限后仍未得到确认,则该数据包被视为丢失。

    超时时间取决于数据包中的内容 - 例如,建立加密和设置连接所需的数据包的超时时间较短,因为它们对于 QUIC 握手性能至关重要。

  • 当一个数据包被视为丢失时,丢失的帧将在具有新序列号的新数据包中重新传输。
  • 数据包接收方使用数据包上的流 ID 和偏移量以正确的顺序组装传输的数据。 数据包编号仅指示发送的顺序,而不是数据包的组装方式。
  • 由于接收器处的数据组装与传输顺序无关,因此丢失或延迟的数据包只会影响其包含的单个流,而不会影响连接中的所有流。 这消除了影响 HTTP/ 1.x和 HTTP/2的队头阻塞问题,因为流不是传输层的一部分。

关于损失检测的完整描述超出了本入门书的范围。 有关确定超时的机制以及允许传输中未确认数据量的详细信息,请参阅RFC 9002QUIC 丢失检测和拥塞控制

妥善管理客户端 IP 地址变更

客户端的 IP 地址(在应用会话中称为源 IP 地址)在会话期间可能会发生变化,例如当 VPN 或网关更改其公共地址或智能手机用户离开 WiFi 覆盖的位置时,会强制切换到蜂窝网络。 此外,网络管理员传统上为 UDP 流量设置的超时时间低于 TCP 连接的超时时间,这会增加网络地址转换(NAT) 重新绑定的可能性。

QUIC 提供了两种机制来减少可能导致的中断:客户端可以主动通知服务器其地址将要更改,服务器可以正常处理客户端地址的计划外更改。 由于连接 ID 在转换过程中保持一致,未确认的帧可以重新传输到新的 IP 地址。

QUIC 会话期间对源 IP 地址的更改可能会给使用源 IP 地址和端口来确定哪个上游服务器要接收特定 UDP 数据报的下游负载均衡器(或其他第 4 层网络组件)带来问题。 为了确保正确的流量管理,第 4 层网络设备提供商需要对其进行更新以处理 QUIC 连接 ID。 要了解有关负载均衡和 QUIC 的未来的更多信息,请参阅 IETF 草案QUIC-LB: 生成可路由的 QUIC 连接 ID

加密

连接建立中,我们提到,初始 QUIC 握手不仅仅是建立连接。 与 TCP 的 TLS 握手不同,UDP 的密钥和 TLS 1.3 加密参数的交换是作为初始连接的一部分进行的。 当客户端恢复先前的连接时,此功能可消除多次交换并实现零往返时间 (0-RTT)。

图 4. TCP+TLS/1.3 与 QUIC 加密握手的比较

除了将加密握手纳入连接建立过程之外,QUIC 还加密了比 TCP+TLS 更大一部分的元数据。 甚至在发生密钥交换之前,初始连接数据包就已经被加密;尽管窃听者仍然可以得出密钥,但这比未加密的数据包需要更多的努力。 这可以更好地保护与攻击者和潜在的国家级审查者都相关的数据,例如服务器名称指示器(SNI)。 图 5 说明了 QUIC 如何加密比 TCP+TLS 更敏感的元数据(红色)。

图 5. QUIC 比 TCP+TLS 加密更敏感的元数据

QUIC 有效负载中的所有数据均使用 TLS 1.3 加密。 有两个优点:不允许使用较旧、易受攻击的密码套件和散列算法,并且强制使用前向保密 (FS) 密钥交换机制。 前向保密可防止攻击者解密数据,即使攻击者捕获了私钥和流量的副本。

低 RTT 和零 RTT 连接可减少延迟

减少在传输任何应用数据之前必须发生在客户端和服务器之间的往返次数可以提高应用的性能,特别是在延迟较高的网络上。

TLS 1.3 引入了单次往返来建立加密连接,以及零次往返来恢复连接,但对于 TCP 来说,这意味着握手必须在 TLS Client Hello 之前发生。

由于 QUIC 将加密操作与连接设置相结合,它提供了真正的 0-RTT 连接重新建立,客户端可以在第一个 QUIC 数据包中发送请求。 通过消除第一个请求之前建立连接的初始往返,可以减少延迟。

图 6. 比较使用 TCP+TLS 与 QUIC 重新建立连接所需的消息

在这种情况下,客户端发送一个使用前一次连接中使用的参数加密的 HTTP 请求,并且为了地址验证目的,还包括服务器在前一次连接期间提供的令牌。

不幸的是,0-RTT 连接恢复不提供前向保密,因此初始客户端请求不像交换中的其他流量那样安全加密。 第一个请求之后的请求和响应受到前向保密的保护。 可能更成问题的是,初始请求也容易受到重放攻击,攻击者可以捕获初始请求并将其多次重放到服务器。

对于许多应用和网站来说,0-RTT 连接恢复带来的性能提升超过了这些潜在的漏洞,但这是您需要自己做出的决定。

NGINX 默认禁用此功能。要启用它,请将ssl_early_data指令设置为on

使用Alt-Svc标头从 HTTP/1.1 迁移到 HTTP/3

几乎所有客户端(尤其是浏览器)都通过 TCP/TLS 建立初始连接。 如果服务器支持 QUIC+HTTP/3,它会通过返回包含Alt-Svc标头的h3参数的 HTTP/1.1 响应来向客户端发出该事实的信号。 然后,客户端选择是否使用 QUIC+HTTP/3 或坚持使用早期版本的 HTTP。 (有趣的是, RFC 7838中定义的Alt-Svc标头早于 QUIC,并且也可以用于其他用途。)

图 7. 如何使用Alt-Svc标头将连接从 HTTP/1.1 转换为 HTTP/3

Alt-Svc标头告诉客户端在备用主机、协议或端口(或它们的组合)上可以找到相同的服务。 此外,还可以告知客户该项服务可以持续提供多长时间。

一些例子:

Alt-Svc: h3=":443" 此服务器的端口 443 上提供 HTTP/3
Alt-Svc: h3="new.example.com:8443" HTTP/3 在服务器new.example.com 的端口 8443 上可用
Alt-Svc: h3=":8443"; ma=600 此服务器的 8443 端口支持 HTTP/3,至少可使用 10 分钟

虽然不是强制性的,但在大多数情况下,服务器都配置为响应与 TCP+TLS 相同端口上的 QUIC 连接。

要配置 NGINX 以包含Alt-Svc标头,请使用add_header指令。 在此示例中, $server_port变量表示 NGINX 在客户端发送其 TCP+TLS 请求的端口上接受 QUIC 连接,86,400 表示 24 小时:

add_header Alt-Svc 'h3=":$server_port";马=86400';

结论

本博客提供了有关 QUIC 的简化入门知识,希望能为您提供足够的概述,以了解 QUIC 使用的关键网络和加密操作。

要更全面地了解如何为 QUIC + HTTP/3 配置 NGINX,请阅读我们博客上现在可用于预览 NGINX QUIC+HTTP/3 实现的二进制包,或观看我们的网络研讨会“亲身体验 NGINX 和 QUIC+HTTP/3” 。 有关 QUIC+HTTP/3 的所有 NGINX 指令的详细信息以及安装预构建二进制文件或从源代码构建的完整说明,请参阅NGINX QUIC 网页


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”