Quic协议与DNS

Quic全称为Quick UDP Internet Connections, 通过字面意思的话我们也可以看出本身Quic协议是基于UDP实现的一种面向互联网的连接协议,至于是否是一种快速(Quick)的数据交换协议,还需要看一下具体的实现与常规协议的比较。


Quic简介
Google于2014年前后开发完成了Quic协议的初期版本该版本后来被称为“gQuic协议”,开发的目的是鉴于传统的HTTPS(TCP+TLS)在具体实现上通信开销较大的问题,为了降低页面的载入延迟、提升传输效率。2016年IETF建立了Quic工作组,推动该协议的标准化工作。由于IETF推动的Quic协议,在社区贡献下逐步完善实现,已经与早期的gQuic具有较大的差异,因此也被称为“IETF Quic协议”,也就是我们现在讲的Quic协议。

既然已经了解到Quic协议要解决的问题,那我们看下当前的Quic已经实现了哪些功能。


首先协议级别提供的安全传输方式,相对于TCP仅解决传输的目的,Quic协议提供的认证和加密功能与传统的TLS提供的基本一致,而传统的TCP+TLS的模式下,TCP握手是独立完成,TLS握手也是独立完成,因此需要至少2次往返才可以实现,而Quic协议则将这个过程进行了压缩,仅一次往返就可以完成整个传输过程,有效的降低了握手的传输延时开销。 数据交互示意图如下面所示,左图代表TCP+TLS的传输过程,右图代表的就是Quic的传输过程。 

其次,使用传统的HTTP1.1版本下,如果需要传输多个文件,需要建立多个连接,而使用HTTP2.0则通过建立复用数据流的方式,允许在一个TCP连接下传输多个文件,但是这样存在的最大的问题就是,同一个数据流数据共享错误处理方式。比如数据流中任一文件存在数据包丢失,会导致整个传输流被阻塞,直到数据被重传后才可以,这种问题又被称为Head Of Line Blocking(队头阻塞)。

Quic协议则通过另一种方式解决该问题,在一个连接的数据流中,将独立的数据传输流进行分割成单独的小的Quic传输流,而这些Quic传输流之间的数据错误不会影响其他Quic数据流传输。这样在网络状态不稳定的情况下,会大幅度的提升数据传输的效率。


Quic协议实现基于UDP传输,相对于传统传输层不同的是,TCP和UDP均处于内核运行态下工作,而Quic则处于用户态下实现,这带来的灵活性在于任何特性的变更不再需要依赖于底层操作系统,而是通过程序的修改既可以实现。
总结一下Quic协议带来的优势:

  1. 握手流程优化带来的高效的数据传输
  2. 等价于TCP+TLS的安全数据传输
  3. UDP协议面向无连接相对于TCP协议带来的性能提升。
  4. 解决HOL队头拥塞问题

Quic存在的问题?
尽管Quic提供了很多对于现有协议的改进,但是本身也存在一些问题: 

  1. NAT问题,简单来讲传统的网络环境,特别是对于网络NAT路由设备访问条件下,可以通过4元组的方式记录连接信息(源地址,源端口,目的地址,目的端口),追踪连接的建立和终止。但是Quic协议由于无法被当前的路由设备识别,因此无法追踪这些信息,而缺省使用比较短的连接超时机制,这就会导致本来已经建立好的连接,因超时而被中断,需要重新建立连接,重新建立的连接则使用不同的UDP端口去访问服务器,导致连接回话无法保持。
  2. 连接迁移问题, Quic协议本身提供连接迁移功能,也就是可以让Client端迁移到指定的Server进行数据传输,内部则通过连接ID的方式进行管理,Server通过该ID来识别一个连接,而不是通过上面的四元组来区分。但是当前网络上采用的AnyCast广播方式,往往一个IP对应多台或者上百台服务器,对于还不支持Quic的边缘路由器将不同的四元组路由到不同的服务器上,而不是基于ID来进行管理
  3. 数据压缩问题: 传统的HTTP2.0采用的压缩方式,往往需要依赖于上下传输的信息,因此要求数据传输有序到达才能进行解码,GoogleQuic也是采用该方式确保复用原有的编解码方式。但是这样的有序保证也带来了队头阻塞这样的问题。而Quic(IETFQuic)则通过修改现有的压缩机制(QPack)解决上述的问题,这样带来的则是实现上的复杂性。 
  4. 放大攻击问题,这也是UDP传输的通病,特别是基于DNS的放大攻击经常会导致一些特别严重的网络事件发生。Quic协议基于UDP,在这方面也不例外,第一次握手期间,服务器发送的证书信息往往比客户端发送的建立连接的请求要大得多,为了缓解该问题:第一种方式是强制Quic客户端发送的初始化握手信息进行填充,满足最小数据包要求。第二种方式引入了额外的源地址验证措施,通过额外的一次握手往返解决该问题(服务器发送一条严格要求客户端进行回传的Token信息,确保客户端能够正常的返回该信息)。第三种则采用更小的证书比如ECDSA证书进行第一次数据交互。
  5. 性能问题:传统的网络连接模型,采用了大量的优化措施来提升TCP的传输性能,而对于UDP则实现的较少,Quic服务性能的提升还有很大的空间,但是对于当前Linux系统也在逐步的去提升UDP的性能,比如使用零拷贝的网络传输以及多UDP数据包卸载等措施来降低用户态和内核态数据传输的性能,

0-RTT模式
在TLS1.3版本中,协议提供一种高效的连接恢复模型,可以降低TLS握手的流程,本质上是通过之前建立的握手信息,进行验证并恢复连接,但是这种“0-RTT”并非真正的不需要建立握手流程,毕竟第一次连接过程仍旧需要。但是Quic协议基于TLS1.3版本实现,支持其0-RTT的,直接在第一次Quic握手请求发送后发送相关数据信息,服务器将Quic握手信息及数据信息一起发送给客户端。
但是一切都是有代价的,互联网上的协议总是在不断权衡中寻找发展,0-RTT的代价就是初次建立连接时候的密钥信息可能会被泄漏(后续仍旧保持加密传输的过程),同时第一次传输的时候携带的数据也可能被攻击者获取并进行重放攻击(尽管无法查看详细的数据信息),这对于一些非幂等的系统设计会导致严重的后果。

Quic协议与DNS

DNS的隐私保护方面这几年有很多的解决方案,比如DoT, DoH等技术,实现起来基本上都是依赖于TLS,而现在的Quic本身也是基于TLS来实现的,相对于之前的技术方案又有什么区别呢?简单来讲就是性能,前两种都是基于TCP+TLS的模式,这种模式代价就是上面提到的建立连接复杂,开销较大,同时本身TCP受限于MTU最大的数据传输单元限制,不如UDP传输灵活高效。

另外Quic协议提供额外的错误纠正,可使得传输更有效(无需重传,直接通过纠正码进行数据恢复)。
当然DoH这种方式最大的优势除了直接复用了HTTP协议当前的优点外,隐藏性还是很高的,而DNS over Quic往往是独立的端口上进行数据传输,仍旧存在中间被阻塞或者监听(加密数据)的情况。


DNSoverQuic的方式,一般直接在Quic协议上进行DNS数据包传递,而不是借助于Quic+HTTP的模式,开发方面的话Go语言提供Quic-go库,rust语言提供了Quiche都可以用来提供协议的封装。

我们可以先通过routdns来看以下Quic协议的数据交互,执行下面的命令来安装一个Quic代理,本地启动一个15353端口,routedns将该dns信息传递到上层的adgard的开放DNS over Quic服务器上。

GO111MODULE=on go get -v github.com/folbricht/routedns/cmd/routedns
routedns config.toml 

其中的config.toml文件:

[resolvers.adguard-doq] 
address = "dns-unfiltered.adguard.com:784" 
protocol = "doq" 
[listeners.local-udp] 
address = "127.0.0.1:15353" 
protocol = "udp" 
resolver = "adguard-doq" 


启动后执行dig命令查询本地的15353端口,即可,通过wireshark我们可以看到QUIC协议的数据交互。这里需要下载 一个IETF Quic的Profile文件 https://www.cellstream.com/resources/wireshark-profiles-repository 搜索下载即可。数据包的格式如下所示:

可对照相关的RFC以及数据包来了解一下Quic的交互流程。同时如果需要手动编写代码来完成域名信息的查询,可以参考routedns项目中的相关示例代码。

相关资源: 
[1] https://www.ietf.org/archive/id/draft-ietf-quic-transport-33.txt IETF Quic协议实现
[2] https://tools.ietf.org/html/draft-huitema-quic-dnsoquic-07 IETF DNS Over Quic协议
[3] https://blog.cloudflare.com/head-start-with-quic/ Cloudflare 2018-09-25
[4] https://blog.cloudflare.com/the-road-to-quic/     Cloudflare 2018-07-26
[5] https://en.wikipedia.org/wiki/Head-of-line_blocking  Wikipedia 队头拥塞
[6] https://github.com/quicwg/base-drafts/wiki/Implementations  Quic的相关实现方式
[7] https://github.com/cloudflare/quiche Quic的Rust实现库
[8] https://github.com/folbricht/routedns/blob/6233f5d3f29966c03f9c15334365f414e9b23faa/doqclient.go RouteDNS DoQ代码示例

发表评论

电子邮件地址不会被公开。 必填项已用*标注