== QUIC (Quick UDP Internet Connnect)

TLS防止中间人攻击的主要方式是基于证书验签机制

为什么放弃TCP

基于TCP的传统体系已运行几十年,形成了僵化的网络基础设施,打补丁升级非常困难,往往其中一环没有升级就可导致整条路走不通。
Google另辟蹊径使用UDP来创建新协议
IETF 把QUIC作为HTTP3的基础通讯设施

TCP+TLS的不足

  • TCP建立连接的3次握手消耗1RTT(最后1个Ack可以随数据一起发送)
  • TLS握手需要消耗2RTT
  • 用户数据量较小,假设只需要1个RTT

总共消耗4RTT,传输效率仅25%,握手环境共消耗3RTT

传统优化手段

  • TLS握手连接复用,减少TLS握手的数据交互次数。曾经握手成功并在有效期内,可省出1个RTT
  • TCP优化,tcp fastopen,但需要较高的内核版本

QUIC 优化手段(gQuic,谷歌版)

1RTT握手

1.服务端持有0-RTT公私钥对,生成SCFG(服务端的配置信息对象),把公钥放入SCFG
2.客户端初次请求时,向服务端获取0-RTT公钥,消耗1个RTT

客户端 -> 服务端
            |
缓存< -- O-RTT公钥		

3.客户端生成临时公私钥对,将临时私钥与服务端的0-RTT公钥根据DH密钥交换算法生成一个加密密钥K1
使用K1加密数据同时将临时公钥一起发送到服务端(用户数据已发送)

DH交换算法基于离散对数,有效性依赖于计算离散对数的难度,其含义是:当已知大素数p和它的一个原根a后,对给定的b,要计算i被认为是很困难的,而给定i计算b却相对容易

4.服务端收到使用K1加密的数据和临时公钥后,会使用0-RTT私钥和客户端的临时公钥通过DK算法计算出K1并解密
5.服务端生成临时公私钥对,使用临时密钥的私钥和客户端的临时公钥生成K2,并加密服务端数据,将临时公钥和K2加密后的数据发送给客户端 (服务端临时私钥(非SCFG私钥)不泄密,则数据就是安全的)
6.客户端收到服务端发送的临时公钥后使用DH算法把服务端的临时公钥和客户端的临时私钥计算出K2,并解密数据
7.后续使用K2进行数据层加解密

这个过程中,公私钥有点多,首先是服务端持有的针对所有人的0-RTT公私钥,其次是客户端的临时公私钥C-TPPK,通过C-TPPK的私钥和0-RTT公钥计算K1加密首包数据,再次是服务端的临时公私钥S-TPPK,通过S-TPPK的私钥和C-TPPK的公钥计算出K2来加密服务端响应,后续长期使用K2作为对称加密密钥。
增加Server端-TPPK生产K2的作用是增强安全性:服务端的0-RTT 公私钥的面向所有人开放的一旦其私钥泄漏则所有连接都不再安全,所有消息都可以解密,使用K2则解决了这个问题 (前向安全问题)

0RTT握手

如果客户端曾经和服务端建立过连接,则客户端可以缓存0-RTT的公钥,直接开启发生数据,也就是1RTT握手流程中前两步直接略过,只要0-RTT私钥未过期,服务端都会承认

  • 开启0RTT安全性上会有所下降,因为0-RTT必然存在重放攻击??todo

由IETF主导的HTTP加密使用的是TLS1.3,握手阶段报文细节有些不一样,todo

安全性分析

  • Quic协议是一个新生的协议,体系还未完全成熟,各个厂商因地制宜仅实现部分特性,因此使用开源产品时需谨慎
    SCFG的安全性:防止中间人攻击,给数据增加签名机制,设置过期时间保障安全性,签名由公有证书的私钥签,客户端对证书验签,由于签名会消耗大量计算资源,可借此进行Dos攻击让服务端瘫痪,因此生产环境需要硬件加速卡来offload签名
  • 0-RTT公钥泄漏带来的安全性问题
    数据被持续抓包后,等到拿到服务端私钥(私钥泄密),则数据包可以直接解密
  • 0-RTT重放攻击
    0RTT让客户端可以不经过获取服务端公钥的流程,服务端也默许这样做,那么当攻击者劫持到流量后可疯狂重新打流量,此时服务端无法识别流量是否合规,因此0RTT的使用场景应该受限至幂等操作如GET\HEAD请求上

非0-RTT数据包有源地址验证能力,在有风险的场景下可以发起源地址挑战验证

STK机制:客户端第一次发送数据包时服务端会根据数据包的源地址和服务器的时间戳等因子生成一个源地址Token,随后和响应包一起发送到客户端,后续的数据传输过程中客户端需要透传STK到服务端,服务端可借此校验。出于性能考虑,只会在源地址和连接ID对应关系变化后才发起挑战
TOKEN可抵挡源地址欺骗攻击和UDP放大攻击(DNS Resolver是基于UDP的,通过向开放的DNS解析器提出请求并提供欺骗性的IP地址,实际的IP地址随后将收到来自DNS的响应,为了产生大量流量,攻击者在构造请求时,会以让DNS解析器尽可能产生大响应为目的,目标会接受到初始流量放大的结果,而且网络被虚假流量堵塞,导致拒绝服务)
服务端可通过NEW_TOKEN帧预先发放令牌,以便后续的新连接使用,这是实现0-RTT的基础,当网络路径变化时,提供连接迁移避免连接中断,通过路径验证验证新地址的可达性
出于性能方面的考虑,源地址发生改变后并不马上终止传输进行地址验证挑战,而是继续传输,在随后发起验证挑战,在空档期启用限速来缓解

无队头阻塞的多路复用

HTTP2实现了多路复用,可以在一条TCP流上并发多个HTTP请求,但基于TCP的HTTP2在传输层却有个问题,TCP无法识别不同的HTTP2流,实际收数据仍是一个队列,当后发的流先收到时,会因前面的流未到达而被阻塞。QUIC一个connection可以复用传输多个stream,每个stream之间都是独立的,一个stream的丢包不会影响到其他stream的接收和处理。

适用场景

弱网环境
秒开率提升、访问加速
加密、安全
小文件下载

transport parameter

  • max_idle_timeout 对端通知自己的timeout,实际以对端较小的值为准 https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-10.1
    max_udp_payload_size :默认65527,低于1200的配置将无效
    initial_max_stream_data_bidi_local 本地初始化的双向流的流速限制
    initial_max_stream_data_bidi_remote 对端打开的双向流的初始流速限制
    initial_max_stream_data_uni 单向流的初始流速控制
    initial_max_streams_bidi 对端可以发起的双向流数,为0时对等方无法开启双向流
    initial_max_streams_uni 对端可以发起的单向流数
    ack_delay_exponent 确认延迟指数
    max_ack_delay 最大确认延迟
    disable_active_migration 禁用传输层迁移
    preferred_address
    active_connection_id_limit
    initial_max_data 连接上可以发送的最大数据量

Streams can also be long-lived and can last
the entire duration of a connection

单测

reuse stream(n)定义:单个quic连接上创建n个stream,round-robin 轮询复用stream

Echo 512byte 客户端50个线程 单连接

场景 stream 个数 TPS 说明
netty TCP + TLS1.2 - 10000 单核cpu低于90%
quic one request one stream 5000 单核cpu100%
quic reuse stream(5) 8200 (7972) 单核cpu100%
quic reuse stream(10) 7400 单核cpu100%
quic reuse stream(20) 6041 单核cpu100%

Echo 1000byte 客户端50个线程 单连接

场景 stream 个数 TPS 说明
netty TCP + TLS1.2 - 8500~9000 单核cpu低于90%
netty quic(自带TLS 1.3) reuse stream(5) 6300 单核cpu100%
netty quic(自带TLS 1.3) reuse stream(10) 6200 单核cpu100%
netty quic(自带TLS 1.3) reuse stream(20) 5500 单核cpu100%

echo 1000byte 客户端200个线程 单连接

场景 stream 个数 TPS 说明
netty TCP + TLS1.2 - 9500 单核cpu低于90%
netty quic(自带TLS 1.3) reuse stream(5) 7500 单个连接上创建5个stream,round-robin 轮询复用stream
netty quic(自带TLS 1.3) reuse stream(10) 6792 单个连接上创建10个stream,round-robin 轮询复用stream

查看网络MTU
ping -s 1472 -M do xx

cubic/bbr/reno

加密套件:
TLS_AES_128_GCM_SHA256 quic
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

linux version

首先,UDP是无状态的,收包和发包都无需事务,协议栈对UDP的处理,从来都是单个报文粒度的,因此只需要保护唯一的socket接收队列即可,即 sk_receive_queue 。
2.6.25 版本内核引入了二级锁backlog队列,4.10版本后进行了优化
https://kernelnewbies.org/Linux_4.10

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=850cbaddb52dfd4e0c7cabe2c168dd34b44ae0

nr readers Kpps (vanilla) Kpps (patched)
1 170 440
3 1250 2150
6 3000 3650
9 4200 4450
12 5700 6250

MTU :链路层上数据帧中数据的最大值,即IP数据报的整个值。数据进入协议栈的封装过程。
MSS:TCP报文段中数据的最大值----MSS选项只能出现在SYN报文中。

查看缓冲区大小
cat /proc/sys/net/core/rmem_max
rmem_max: 个Socket的读缓冲区可由程序设置的最大值,单位字节 212992
wmem_max:一个Socket的写缓冲区可由程序设置的最大值,单位字节;212992
rmem_default:一个Socket的被创建出来时,默认的读缓冲区大小,单位字节;212992
wmem_default:一个Socket的被创建出来时,默认的写缓冲区大小,单位字节; 212992

套接字缓冲区参考文章:https://blog.csdn.net/daaikuaichuan/article/details/83061726

posted on 2021-02-03 15:35  j.liu&nbsp;windliu  阅读(495)  评论(0编辑  收藏  举报