quic协议

        QUIC全称为Quck UDP Internet Connections,即快速UDP网络连接,是一种新的默认加密互联网传输协议,它提供了一些改进,旨在加快HTTP流量并使其更安全,其目标是最终取代网络上的TCP和TLS。其实,有两个协议同名:“谷歌QUIC”(简称“gQUIC”)是谷歌工程师几年前设计的原始协议,经过多年的实验,现已被IETF(互联网工程工作队)用于标准化。“IETF QUIC”已经与gQUIC有很大不同,因此可以被视为一个单独的协议。从数据包的有线格式到HTTP的握手和映射,QUIC通过许多组织和个人的公开协作,改进了原始的gQUIC设计,其共同目标是使互联网更快、更安全。2018年10月,IETF的HTTP和QUIC工作组共同决定将QUIC上的HTTP映射称为HTTP/3,以使其在全球范围内标准化。

quic特性

高安全与性能

        QUIC与现在备受尊重的TCP的一个更根本的不同之处在于,它设计目标是提供一个默认安全的传输协议。QUIC通过提供身份验证和加密等安全特性来实现这一点,而这些特性现在通常由比传输协议更高层次的协议(如TLS)处理。

         最初设计的QUIC协议握手连接将TCP经典的三次握手与TLS 1.3握手结合在一起,后者提供端点的身份验证以及密码参数的协商。QUIC用它自己的帧格式替换TLS记录层,同时保持相同的TLS握手消息。这不仅确保了连接始终经过身份验证和加密,还使初始连接的建立更快:与TCP和TLS 1.3握手相结合所需的两次往返相比,典型的QUIC握手只需客户端和服务器之间的一次往返即可完成。之后,QUIC更进一步,还加密了中间框可能滥用的其他连接元数据来干扰连接。例如,当使用连接迁移时,被动路径攻击者可以使用数据包号将用户活动关联到多个网络路径上。通过加密数据包号,QUIC确保它们不能用于关联连接端点以外的任何实体的活动。

队头阻塞的避免

        HTTP/2提供的主要改进之一是能够将不同的HTTP请求多路传输到同一个TCP连接上。这允许HTTP/2应用程序并发处理请求,并更好地利用可用的网络带宽。这是一个很大的改进,在这之前如果应用程序想要同时处理多个HTTP/1.1请求,就需要发起多个TCP+TLS连接(例如,当浏览器需要同时获取CSS和Javascript资源来呈现一个网页时)。但是创建新的连接需要重复多次握手初始化,也会使拥塞窗口上升,这意味着网页的渲染速度减慢。多路复用HTTP交换避免了这一切。但是这也会造成队头阻塞,由于多个请求/响应在同一个TCP连接上传输,当一个TCP分节丢失时,之后所有的包都会受到影响。

       而QUIC不仅为多路复用提供很好的支持,不同的http流可以映射到不同的QUIC的传输流,且它们仍然共享相同的QUIC连接,因此不需要额外的握手并共享拥塞状态。此外QUIC流是独立交付的,因此在大多数情况下,一个流的数据包丢失不会影响其他流的传输。

QPACK

       QUIC可以独立地在不同流上发送多个HTTP请求(或响应),这意味着虽然就单个流而言,它负责按顺序交付数据,但没有跨多个流的顺序保证。例如,如果客户端通过QUIC流A发送HTTP请求A,并通过流B发送请求B,由于数据包重新排序或网络丢失,服务器可能会在请求A之前收到该请求B,如果请求B编码为引用请求A的标头,服务器将无法解码它,因为它尚未看到请求A。

       在gQUIC协议中,只需在同一gQUIC流上序列化所有HTTP请求和响应标头(但不是主体),即可解决这个问题,这意味着无论发生什么,标头都会按顺序交付。这是一个非常简单的方案,允许实现重用其许多现有的HTTP/2代码,但另一方面,它增加了QUIC旨在减少的首行阻塞。因此,IETF QUIC工作组设计了HTTP和QUIC(“HTTP/QUIC”)之间的新映射,以及名为“QPACK”的新标头压缩方案。

       在HTTP/QUIC映射和QPACK规范的最新草案中,每个HTTP请求/响应交换都使用自己的双向QUIC流,因此没有行头阻止。此外,为了支持QPACK,每个对等方创建了两个额外的单向QUIC流,一个用于向另一个对等方发送QPACK表更新,另一个用于确认另一方收到的更新。这样,QPACK编码器只有在解码器明确承认后才能使用动态表引用。

偏转反射

        基于UDP的协议中的一个常见问题是它们容易受到反射攻击(reflection attacks),即攻击者通过欺骗数据包的源 IP 地址,使其看起来像是来自受害者,从而欺骗原本无辜的服务器向第三方受害者发送大量数据。

        QUIC 的握手是非常不对称的:像TLS 一样,在它的第一次传输中,QUIC 服务器通常发送自己的证书链(certificate chain),它可能非常大,而客户端只需发送几个字节(TLS ClientHello消息嵌入 QUIC 包中)。因此,客户端发送的初始 QUIC 包必须填充到特定的最小长度(即使包的实际内容要小得多)。QUIC协议还定义了一个显式的源地址验证(source-address verification)机制,在该机制中,服务器不发送长响应,只发送一个很小的“重试(retry)”包,其中包含一个唯一的加密令牌,然后客户端必须在新的初始包中向服务器回显。这样,服务器就有了更高的信心,即客户机不会欺骗自己的源IP地址(因为它收到了重试数据包,即retry packet),并且可以完成握手。这样带来的缺点是,它将初始握手持续时间从1次RTT增加到2次。

服务端

Nginx HTTP/3

        2020年6月10日,NGINX 宣布了 NGINX 的官方 QUIC 和 HTTP/3 实现的初始版本,即 http_v3_module。这是一个技术预览,应该被视为实验性的,它不适用于生产环境。在Nginx中启用QUIC,需要将进行如下配置。

 devsisters/goquic

        goquic是一个开源的QUIC协议的go实现,它基于libquic库,而后者又基于Chromium上的原始QUIC实现。使用这个库首先需要安装go环境,之后从github下载源代码。在这个实现中,其还实现了基于QUIC的http3编程,在example文件夹下,有利用http3实现的客户端与服务端。

        其服务端的运行顺序是先进性编译,然后在终端中在该目录下输入./main -qlog -v -tcp命令,使用-tcp的原因是因为因为浏览器第一次访问时仍然是要通过TCP进行的,如果不带浏览器将无法访问。

客户端

Firefox

        Firefox  75 及以上版本支持 HTTP/3

        启用方法:在地址栏输入 'about:config',配置 network.http.http3.enabled = true。通过 “Web 开发者” 可以看到 “版本:HTTP/3” “协议版本:: TLSv1.3”

Chrome

        Chrome 83 及以上版本支持 HTTP/3,使用命令行增加如下启动参数:

$ ./chrome --enable-quic --quic-version=h3-27

在线检测网站

        https://http3check.net/与https://gf.dev/http3-test

服务端

        goquic已经实现了封装了QUIC协议的http3,当我们使用编写http3的服务器时,与在go中编写http服务器一样,都是首先实现处理器,然后申请一个http3服务器,并且对服务器进行设置,再将处理器与它们负责的URL网址绑定,最后运行服务器即可。

        在example的服务器实现中,首先申请了一个http3服务器,然后对其处理器与其ip地址进行设置,然后调用ListenAndServeTLS,监听设置UDP的地址,并且使用http3来处理到达该地址的请求。在ListenAndServeTLS实现中,首先对参数中的证书进行解析,然后调用serveImpl函数中为服务器 添加了一个 quic.EarlyListener结构体,之后对该结构体使用accept函数,当收到一个新的请求的时候添加到sessionQueue中,返回一个客户端Session,用这个Session可以和客户端进行发送和接收数据不断地监听端口,等待一个新的客户端连接请求。

客户端 

client.RoundTripclient.RoundTripauthorityAddrdialdoRequest

 抓包

        在firefox中打开about:config,搜索HTTP3,将值设为True以打开http3的实验特性。之后在终端运行goquic的服务端实例代码,在浏览器中访问https://localhost:6121/demo/tile网页,通过浏览器的开发者工具可以看见,第一次访问该页面会通过TCP协议进行,通过对响应头的查看,可以发现响应头中带有Alt-Svc字段。Alt-Svc 全称为“Alternative-Service”,直译为“备选服务”。该头部列举了当前站点备选的访问方式列表,再次刷新,浏览器就会以HTTP3协议来访问。

        之后使用wireshark进行抓包,过滤器设置为udp.port==6121。

 

        QUIC的数据包是通过UDP数据报进行传输的,一个数据报中可以包含一个或多个quic数据包。从图中可以看到QUIC数据包被分为四种:初始包,重试数据包,握手包和加密数据包(0-RTT和1-RTT两种类型)。进一步查看数据包内容,可以发现QUIC首部分为两种:Long header 和 Short Header,通过第一个有效字节的最高位来区分。通过查阅QUIC: A UDP- Based Multiplexed and Secure Transport draft-ietf-quic-transport-29这一官方文档,可以进一步得到三种包的结构以及两种头部的详细结构。Long Header Packets的类型包括四种:Initial,0-RTT,Handshake,Retry。在版本协商以及1-RTT密钥传输完成后,quic就会使用Short Header Packet来传输数据。

NP538