Init
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
---
|
||||
tags: [计算机网络, QUIC, HTTP/3, ConnectionID, 0-RTT, QPACK, NAT迁移]
|
||||
create time: 2026-05-18 05:25
|
||||
---
|
||||
|
||||
# QUIC 协议深度解析
|
||||
|
||||
## 概述
|
||||
|
||||
QUIC (Quick UDP Internet Connections) 正在静默地替代 TCP——Google、Cloudflare、Microsoft 的流量中已有超过 40% 走的是 QUIC。理解 QUIC 的设计哲学和具体机制,是把握下一代网络协议的钥匙。
|
||||
|
||||
## 为什么需要 QUIC?
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Problem["TCP 的三大历史包袱"] --> P1["• 拥塞控制算法写死在内核<br/>无法按需调整"]
|
||||
Problem --> P2["• IP/port 变化 → 四元组变化<br/>连接必须重建"]
|
||||
Problem --> P3["• TLS in TCP → 双重握手延迟<br/>三次握手 + TLS 握手 = 最少 2 RTT"]
|
||||
|
||||
Solution["QUIC 的三个解答"] --> S1["✅ 用户态实现 BBRv2 等算法<br/>应用层可编程、随时升级"]
|
||||
Solution --> S2["✅ Connection ID 独立于 IP/port<br/>WiFi↔5G 无缝切换"]
|
||||
Solution --> S3["✅ 内置 TLS 1.3 + 0-RTT<br/>首次握手即加密, 续连零延迟"]
|
||||
|
||||
style Problem fill:#FFD700,color:#000
|
||||
style Solution fill:#98FB98,color:#000
|
||||
```
|
||||
|
||||
### TCP vs QUIC 核心对比
|
||||
|
||||
| 维度 | TCP + TLS | QUIC + TLS 1.3 |
|
||||
|------|-----------|---------------|
|
||||
| 传输协议 | TCP (可靠,面向字节流) | UDP (不可靠,自带可靠性层) |
|
||||
| 拥塞控制 | 内核固定 (cubic/bbr) | 用户态可插拔 (BBRv2, NewReno...) |
|
||||
| 连接标识 | 四元组 (src_ip, src_port, dst_ip, dst_port) | Connection ID (16 bytes, 自定义) |
|
||||
| 握手延迟 | 1 RTT (TCP) + 1 RTT (TLS 1.3) = 2 RTT | 1 RTT 完成 TCP+TLS (0-RTT 可选) |
|
||||
| 多路复用 | 应用层自己搞 (HTTP/2) | 原生支持多 Stream,零队头阻塞 |
|
||||
| NAT 穿透 | 断开重连 | CID 不变,自动迁移 |
|
||||
| 头部压缩 | HPACK (HTTP/2) / QPACK (HTTP/3) | QPACK(解决队头阻塞) |
|
||||
|
||||
## QUIC 的连接管理
|
||||
|
||||
### Connection ID —— NAT 迁移的关键
|
||||
|
||||
```
|
||||
TCP 连接标识 = (src_ip, src_port, dst_ip, dst_port)
|
||||
→ IP 变了 → 四元组变 → 连接断了 ❌
|
||||
|
||||
QUIC 连接标识 = Connection ID (16 bytes,由端方自定义)
|
||||
→ IP/port 变了 → CID 不变 → 连接继续 ✅
|
||||
|
||||
典型场景: iPhone 从 WiFi 切换到 5G
|
||||
旧 IP: fe80::abcd → 新 IP: fe80::efgh
|
||||
TCP: 连接断开,需重连 (RTT × 2)
|
||||
QUIC: CID 不变,数据包自动迁移到新 IP
|
||||
```
|
||||
|
||||
```
|
||||
Connection ID 的生命周期:
|
||||
─────────────────────────
|
||||
Client → Server: Initial Packet {CID: client_cid}
|
||||
Server → Client: Initial Packet {CID: server_cid}
|
||||
Server → Client: Handshake Packet {CID: server_cid}
|
||||
Client → Server: Handshake Packet {CID: client_cid}
|
||||
Client → Server: 1-RTT Packet {CID: client_cid} ← 加密载荷, 业务数据
|
||||
```
|
||||
|
||||
> [!tip] 为什么 QUIC 用 UDP 而不是直接改 TCP?
|
||||
>
|
||||
> 修改 TCP 需要在操作系统内核层面做全球部署,这几乎是不可能的(每个发行版都有不同)。而把可靠性搬到用户态用 UDP 承载——虽然看起来是"倒退"——但部署极其灵活:SDK 更新即可生效。Google 的 QUIC 实现就是作为一个 Chrome 扩展库无声更新的。
|
||||
|
||||
### QUIC Stream 独立性 —— 消除队头阻塞
|
||||
|
||||
```
|
||||
QUIC Stream 独立性:
|
||||
┌──────────────┐
|
||||
│ Stream 0 │ ← 控制通道 (HTTP/3 信令)
|
||||
│ Stream 1 │ ← Request A [✓ Data ✓ ACK] ✅ 正常
|
||||
│ Stream 2 │ ← Request B [✓ Data ✓ ACK] ✅ 正常
|
||||
│ Stream 3 │ ← Request C [✗ Lost 🔄 仅重传此流!]
|
||||
│ Stream 4 │ ← Response A [✓ Data ✓ ACK] ←不受 Stream 3 影响!
|
||||
│ Stream 5 │ ← Request D [🔄 正在发送...]
|
||||
└──────────────┘
|
||||
|
||||
对比 HTTP/2 (共用 TCP 流):
|
||||
请求 C 丢包 → 所有后续请求(包括 D、响应A)都要等 C 重传完成!
|
||||
这就是 HTTP/2 在弱网下不如 HTTP/3 的原因。
|
||||
```
|
||||
|
||||
## 0-RTT 握手与重放攻击
|
||||
|
||||
### 0-RTT 的工作原理
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant S as Server
|
||||
|
||||
Note over C,S: 首次连接: 1-RTT handshake
|
||||
C->>S: ClientHello + HelloRetryRequest + Finished
|
||||
S-->>C: 1-RTT Ready
|
||||
|
||||
Note over C,S: Session 缓存 (Session Ticket)
|
||||
C->>S: ClientHello + 0-RTT data + Finished
|
||||
Note over S: 验证 Session Ticket + 重放检测
|
||||
alt 允许 0-RTT
|
||||
S-->>C: Early data accepted ✅
|
||||
else 拒绝 0-RTT
|
||||
S-->>C: 0-RTT rejected ⚠️ (但仍处理后续数据)
|
||||
end
|
||||
```
|
||||
|
||||
```
|
||||
⚠️ 0-RTT 的重放攻击风险:
|
||||
─────────────────────────
|
||||
攻击者截获了客户端的 0-RTT 请求 (如 POST /transfer?to=hacker&amount=10000)
|
||||
然后把这个请求重新发给服务器
|
||||
服务器认为是新的合法请求 → 重复转账 💸
|
||||
|
||||
防御措施:
|
||||
1. 服务端对 0-RTT 请求标记 "early data"
|
||||
2. 幂等操作 (GET/HEAD/PUT 删除) 可以安全使用 0-RTT
|
||||
3. 非幂等操作需在应用层加防重放 Token
|
||||
4. QUIC 的 MaxEarlyData 字段限制重放窗口大小
|
||||
```
|
||||
|
||||
### HTTP/3 的 QPACK —— 区别于 HPACK
|
||||
|
||||
```
|
||||
HPACK (HTTP/2) 的问题:
|
||||
• Header 压缩表与数据共享同一 TCP 流
|
||||
• 如果 TCP 数据包丢失 → 解压失败 → 所有后续 header 阻塞
|
||||
• 这就是 HTTP/2 的队头阻塞在头部压缩层面的体现
|
||||
|
||||
QPACK (HTTP/3) 的解法:
|
||||
• 压缩表独立于数据流 (unblocked streams)
|
||||
• 即使某个 QUIC stream 丢了,header 仍能正确解码
|
||||
• 通过 encoder/decoder 两侧维护独立的动态表实现
|
||||
```
|
||||
|
||||
## QUIC 连接生命周期
|
||||
|
||||
```
|
||||
QUIC 连接的建立:
|
||||
─────────────────
|
||||
1. Client → Send Initial Packet (无 CID 或临时 CID)
|
||||
2. Server → Send Initial Packet (含 server_cid)
|
||||
3. Server → Send Handshake Packet
|
||||
4. Client → Send Handshake Packet
|
||||
5. Both → Send 1-RTT Packets (加密载荷)
|
||||
6. Connection established ✅
|
||||
|
||||
关闭:
|
||||
─────────────────
|
||||
• QUIC 没有 RST/FIN 标志位
|
||||
• 用 CLOSE_CONNECTION_FRAME 通知对方
|
||||
• 双方确认后关闭
|
||||
• 类似 TCP 的四次挥手但在应用层实现
|
||||
```
|
||||
|
||||
## Go 中的 QUIC 实现
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// QUIC 服务器
|
||||
config := &quic.Config{
|
||||
Tracer: quic.TraceWriter, // 可观测性
|
||||
}
|
||||
listener, err := quic.Listen(nil, nil, config)
|
||||
|
||||
conn, err := listener.Accept(ctx) // 接受新连接
|
||||
stream, err := conn.OpenStream() // 打开一个独立的 Stream
|
||||
stream.Write([]byte("hello")) // Stream 3 的数据不会影响 Stream 5
|
||||
```
|
||||
|
||||
> [!tip] 为什么 Go 原生不支持 QUIC?
|
||||
> Go 的 net/http 标准库目前仍基于 TCP。第三方实现 `quic-go` (by @malgorithms) 是最成熟的 Go QUIC 库,已在多个生产环境使用。Go 官方对加入 QUIC 支持持开放态度。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/HTTPS与TLS握手]] — TLS 1.3 是 QUIC 的安全基础
|
||||
- [[hhs/NETWORK/TCP段结构与状态机]] — QUIC 要替代的正是 TCP 的这些机制
|
||||
- [[hhs/NETWORK/前沿与进阶综述]] — QUIC 在更广泛前沿技术中的位置
|
||||
Reference in New Issue
Block a user