NGINX 中的 QUIC 网络与加密入门

NGINX | 2023年4月19日

NGINX博客首次提及QUIC和HTTP/3是在四年前(!)。和您一样,我们也热切期待QUIC实现即将合并到NGINX Open Source主线分支中。鉴于开发周期如此漫长,如果您一直没怎么考虑过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作为传输协议。与TCP一样,UDP也用于通过IP网络传输数据,但它有意省去了连接建立和可靠传输机制。由于没有这些开销,UDP适用于许多更注重效率和速度而非可靠性的应用。

不过,对于大多数Web应用而言,可靠的数据传输至关重要。由于底层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连接:

图 2. 建立 TCP 连接的三次握手

建立QUIC连接的步骤与此类似,但效率更高。作为加密握手的一部分,连接建立过程还包含地址验证。地址验证可防御流量放大攻击。在此类攻击中,恶意行为者向服务器发送数据包,并将源地址信息伪造成预定攻击受害者的地址。攻击者企图让服务器向受害者发送数量更多或体积更大的数据包,超出攻击者自身所能生成的流量,从而产生足以使受害者不堪重负的流量。(有关更多详细信息,请参阅RFC 9000的第8节《QUIC:基于UDP的多路复用安全传输》。)

在建立连接的过程中,客户端和服务器分别提供独立的连接 ID,并将其编码到 QUIC 标头中,从而无需依赖客户端源 IP 地址即可轻松识别连接。

但是,由于QUIC连接的初始建立过程还包括TLS加密密钥交换操作,因此与建立TCP连接时生成简单的SYN-ACK响应相比,服务器需要投入更多计算资源。这也可能成为分布式拒绝服务(DDoS)攻击的潜在途径,因为在执行密钥交换操作之前,客户端IP地址尚未经过验证。

不过,可以将NGINX的quic_retry指令设置为on,使其在开始复杂的加密运算之前验证客户端IP地址。在这种情况下,NGINX会向客户端发送一个包含令牌的重试数据包,客户端必须在连接建立数据包中包含该令牌。

图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 9002《QUIC 丢包检测和拥塞控制》。

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

客户端的IP地址(在应用会话中称为“源IP地址”)可能会在会话期间发生变化。例如,VPN或网关更改其公共地址,或者智能手机用户离开WiFi覆盖区域,因而被迫切换到蜂窝网络。此外,网络管理员通常为UDP流量设置比TCP连接更短的超时时间,这会增加网络地址转换(NAT)重新绑定的可能性。

QUIC提供了两种机制来减少由此可能造成的中断:客户端可以主动通知服务器其地址即将发生变化,服务器也可以妥善处理客户端地址的意外变化。由于连接ID在转换过程中保持不变,未确认的帧可以重新传输到新的IP地址。

QUIC会话期间源IP地址发生变化,可能会给下游负载均衡器(或其他第4层网络组件)带来问题。这些设备使用源IP地址和端口来确定应由哪台上游服务器接收特定的UDP数据报。为确保正确管理流量,第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显示,与TCP+TLS相比,QUIC会加密更多可能敏感的元数据(以红色标示)。

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

QUIC有效载荷中的所有数据均使用TLS 1.3加密。这有两项优势:不允许使用存在漏洞的旧密码套件和哈希算法,并且必须采用前向保密(FS)密钥交换机制。前向保密可防止攻击者解密数据,即使攻击者获取了私钥和流量副本。

低RTT和零RTT连接可降低延迟

减少传输任何应用数据之前客户端与服务器之间必须进行的往返通信次数,可以提升应用性能,尤其是在延迟较高的网络中。

TLS 1.3将建立加密连接所需的往返次数减少到1次,并可通过0次往返恢复连接;但使用TCP时,必须先完成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,则会返回一个HTTP/1.1响应,并在h3标头的Alt-Svc参数中表明这一点。随后,客户端会选择使用QUIC+HTTP/3,还是继续使用较早版本的HTTP。(值得一提的是,Alt-Svc标头由RFC 7838定义,早于QUIC出现,也可用于其他用途。)

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

The Alt-Svc标头会告知客户端,可通过备用主机、协议或端口(或其组合)使用同一服务。此外,还可告知客户端,在多长时间内可以合理地认为该服务将继续可用。

示例:

Alt-Svc: h3=":443" 此服务器的端口 443 支持 HTTP/3Alt-Svc: h3="new.example.com:8443" 服务器 new.example.com 的端口 8443 支持 HTTP/3Alt-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"; ma=86400';

结论

本文以简明方式介绍QUIC的基础知识,希望能帮助您大致了解QUIC所使用的关键网络和加密操作。

如需全面了解如何为QUIC + HTTP/3配置NGINX,请阅读我们博客上的文章《预览版NGINX QUIC+HTTP/3实现现已提供二进制软件包》,或观看网络研讨会《动手实践NGINX和QUIC+HTTP/3》。如需了解适用于QUIC+HTTP/3的所有NGINX指令,以及安装预构建二进制文件或从源代码构建的完整说明,请参阅NGINX QUIC网页。


分享
标签: Tech, F5 NGINX

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。