vault backup: 2026-05-17 22:27:07
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 在更广泛前沿技术中的位置
|
||||
@@ -0,0 +1,296 @@
|
||||
---
|
||||
tags: [计算机网络, eBPF, XDP, kprobe, Tracepoint, ServiceMesh, Istio, Envoy]
|
||||
create time: 2026-05-18 05:30
|
||||
---
|
||||
|
||||
# eBPF 与 Service Mesh
|
||||
|
||||
## 概述
|
||||
|
||||
eBPF 让 Linux 内核变得可编程而不需要编译内核模块;Service Mesh(Istio/Linkerd)让微服务间的流量治理从代码中剥离到基础设施层。这两者代表了可观测性和流量治理的两个重要方向。
|
||||
|
||||
## eBPF(extended Berkeley Packet Filter)
|
||||
|
||||
### 什么是 eBPF?
|
||||
|
||||
eBPF 允许在**内核态安全地运行沙箱程序**,无需修改内核源码或加载内核模块。它最初是一个 packet filter 增强版,现在已扩展到 tracing、monitoring、security 等多个领域。
|
||||
|
||||
```
|
||||
传统方案: eBPF 方案:
|
||||
───────── ─────────
|
||||
编译内核模块 (.ko) ↔ 编写 eBPF C 程序
|
||||
手动符号导出 ↔ kprobe/kfunc/tracepoint 直接挂钩
|
||||
系统重启加载 ↔ insmod bpf 即时生效
|
||||
无类型安全检查 ↔ 内核验证器严格检查 (verifier)
|
||||
崩溃影响全局 ↔ 沙箱隔离,失败不崩内核
|
||||
无法卸载或调试 ↔ 可卸载,bpftool 可调试
|
||||
```
|
||||
|
||||
### eBPF 的核心架构
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "用户态 (User Space)"
|
||||
Prog["eBPF 程序<br/>(C/BPF 汇编)"]
|
||||
Map["bpf_map<br/>KV 存储<br/>hash/array/perf_event/..."]
|
||||
Tool["bpftool<br/>BCC tools<br/>bpftrace"]
|
||||
end
|
||||
|
||||
subgraph "内核态 (Kernel Space)"
|
||||
Verifier["Verifier<br/>严格安全检查"]
|
||||
JIT["JIT Compiler<br/>C → x86/arm/riscv 机器码"]
|
||||
|
||||
subgraph "Attachment Points"
|
||||
KP["kprobe / kretprobe<br/>hook 任意内核函数入口/出口"]
|
||||
TP["tracepoint<br/>内核预定义探测点"]
|
||||
TC["Traffic Control<br/>qdisc classifier"]
|
||||
XDP["XDP (eXpress Data Path)<br/>网卡驱动级别最快路径"]
|
||||
end
|
||||
|
||||
Map -->|"写入"| PerfEvent["perf event → 用户态采集"]
|
||||
end
|
||||
|
||||
Tool -->|"加载"| Prog
|
||||
Prog -->|"提交"| Verifier
|
||||
Verifier -->|"验证通过"| JIT
|
||||
JIT -->|"注册到"| KP
|
||||
JIT -->|"注册到"| TP
|
||||
JIT -->|"注册到"| TC
|
||||
JIT -->|"注册到"| XDP
|
||||
```
|
||||
|
||||
### eBPF 在网络中的典型应用层级
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
NIC["Network Interface Card"] -.->|XDP 微秒级处理| XDP["XDP<br/>最快路径: 网卡驱动级别"]
|
||||
|
||||
TC["Traffic Control<br/>tc filter/qdisc"] -.->|中等路径| Stack["Linux Network Stack"]
|
||||
|
||||
Stack -.->|kprobe hook| Kprobe["kprobe/kretprobe<br/>TCP/IP 栈钩子"]
|
||||
|
||||
XDP -->|"drop/mirror/redirect"| NIC
|
||||
TC -->|"police/packet/cgroup"| Stack
|
||||
Kprobe -->|"tcp_connect/tcp_rcv_skb..."| App["可观测性工具"]
|
||||
|
||||
style XDP fill:#FF6B6B,color:#fff
|
||||
style TC fill:#FFD700,color:#000
|
||||
style Kprobe fill:#98FB98,color:#000
|
||||
```
|
||||
|
||||
| 挂载点 | 性能 | 用途 | 典型工具 |
|
||||
|--------|------|------|---------|
|
||||
| **XDP** | 微秒级,最快 | DDoS 过滤、L4 LB、镜像 | AF_XDP, cilium-hubble |
|
||||
| **tc (Traffic Control)** | 纳秒~微秒级 | qdisc 调度、带宽限制、重定向 | tc-bpf, polycube |
|
||||
| **kprobe/kretprobe** | 低开销 | 追踪任意内核函数 | bcc tcpconnect, bpftrace |
|
||||
| **tracepoint** | 极低开销 | 内核预定义事件 | perf trace |
|
||||
| **CGroup** | 进程级 | 网络隔离、限速 | cgroup-bpf |
|
||||
| **LSM** | 安全级别 | 强制访问控制 | libbpf-security |
|
||||
|
||||
### 常用 eBPF 工具实战
|
||||
|
||||
```bash
|
||||
# === BCC Tools (Python-based eBPF 工具集) ===
|
||||
|
||||
# 追踪所有新建 TCP 连接
|
||||
sudo ./tcpconnect
|
||||
PID COMM IP SADDR DADDR DPORT
|
||||
1234 curl 4 192.168.1.10 93.184.216.34 443
|
||||
|
||||
# 查看 TCP 连接存活时间
|
||||
sudo ./tcplife
|
||||
PID COMM FD LADDR:LADDR → RADDR:RADDR state msec
|
||||
1234 chrome 42 192.168.1.10:54321→ 93.184.216.34:443 ESTAB 12345
|
||||
|
||||
# 查看 TCP 状态分布
|
||||
sudo ./tcpstates
|
||||
Tracing TCP states... Output every 1 seconds.
|
||||
TIME_WAIT 42
|
||||
ESTABLISHED 128
|
||||
CLOSE_WAIT 0
|
||||
|
||||
# === bpftrace (一行脚本) ===
|
||||
|
||||
# 实时打印每个 TCP 包的大小
|
||||
sudo bpftrace -e 'kprobe:tcp_sendmsg { @bytes[tid] = arg0; }'
|
||||
|
||||
# 统计每秒 DNS 查询数
|
||||
sudo bpftrace -e 'tracepoint:dns:dns_query { @count++; } interval:s:1 { print(@count); clear(@count); }'
|
||||
|
||||
# === Cilium Hubble (Kubernetes 可观测性) ===
|
||||
|
||||
# 服务间流量可视化
|
||||
hubble observe --namespace=default
|
||||
FLOW 08:23:45.123 DefaultPod → api-server (TCP NEW) DPT=8080
|
||||
FLOW 08:23:45.125 api-server → DefaultPod (TCP FIN)
|
||||
|
||||
# 按命名空间聚合
|
||||
hubble observe --aggregate 5s
|
||||
```
|
||||
|
||||
### eBPF 监控网络问题的经典场景
|
||||
|
||||
```bash
|
||||
# 场景 1: 哪个进程的 TCP 连接最多?
|
||||
sudo bpftrace -e '
|
||||
kprobe:sock_alloc
|
||||
/@pids[comm]/++
|
||||
'
|
||||
|
||||
# 场景 2: 慢连接诊断——每个 TCP 连接耗时多久?
|
||||
sudo bpftrace -e '
|
||||
kprobe:tcp_v4_connect /arg0/ {
|
||||
@start[tid] = nsecs;
|
||||
}
|
||||
kretprobe:tcp_v4_connect /arg0 >= 0/ {
|
||||
$delta = (nsecs - @start[tid]) / 1000;
|
||||
@latency_us = hist($delta);
|
||||
delete(@start[tid]);
|
||||
}'
|
||||
|
||||
# 场景 3: SYN Flood 检测
|
||||
sudo bpftrace -e '
|
||||
kprobe:tcp_v4_do_rcvd
|
||||
@syn_count["SYN_RECV"]++;
|
||||
interval:1s {
|
||||
print(@syn_count);
|
||||
clear(@syn_count);
|
||||
}'
|
||||
```
|
||||
|
||||
## Service Mesh (Istio / Linkerd)
|
||||
|
||||
### Sidecar 模式原理
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Pod["Pod / VM"] -->|"app container"| App["my-service v1"]
|
||||
Pod -->|"proxy container"| Proxy["envoy sidecar"]
|
||||
|
||||
Proxy -->|"xDS API"| CP["Istiod<br/>Pilot(路由) + Citadel(mTLS)"]
|
||||
CP -->|"推送配置"| Proxy
|
||||
|
||||
subgraph "Sidecar 自动接管:"
|
||||
MTLS["mTLS 双向认证"]
|
||||
Retry["重试/熔断/超时"]
|
||||
Tracing["分布式链路追踪 (Jaeger)"]
|
||||
Metrics["指标收集 (Prometheus)"]
|
||||
Canary["金丝雀/蓝绿发布"]
|
||||
Dashboard["流量仪表盘"]
|
||||
end
|
||||
|
||||
Proxy -.-> MTLS
|
||||
Proxy -.-> Retry
|
||||
Proxy -.-> Tracing
|
||||
Proxy -.-> Metrics
|
||||
Proxy -.-> Canary
|
||||
Proxy -.-> Dashboard
|
||||
|
||||
style Proxy fill:#DDA0DD,color:#000
|
||||
style CP fill:#FFD700,color:#000
|
||||
```
|
||||
|
||||
### Istio Traffic Management 核心资源
|
||||
|
||||
```yaml
|
||||
# VirtualService: 路由规则
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
hosts: ["my-service.default.svc.cluster.local"]
|
||||
http:
|
||||
- match:
|
||||
- headers:
|
||||
x-canary: {exact: "true"}
|
||||
route:
|
||||
- destination: {host: my-service, subset: v2} # canary 版
|
||||
weight: 100
|
||||
- route:
|
||||
- destination: {host: my-service, subset: v1}
|
||||
weight: 90 # 正式版 90%
|
||||
- destination: {host: my-service, subset: v2}
|
||||
weight: 10 # 金丝雀 10%
|
||||
timeout: 5s # 超时控制
|
||||
retries: # 重试策略
|
||||
attempts: 3
|
||||
perTryTimeout: 2s
|
||||
retryOn: "5xx,reset,connect-failure,gateway-error"
|
||||
|
||||
# DestinationRule: 定义 subset (版本分组)
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: DestinationRule
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
host: my-service.default.svc.cluster.local
|
||||
trafficPolicy:
|
||||
tls:
|
||||
mode: ISTIO_MUTUAL # 自动 mTLS
|
||||
connectionPool:
|
||||
tcp:
|
||||
maxConnections: 100 # 连接池上限
|
||||
http:
|
||||
h2UpgradePolicy: DEFAULT # 升级到 HTTP/2
|
||||
http1MaxPendingRequests: 100
|
||||
http2MaxRequests: 1000
|
||||
subsets:
|
||||
- name: v1 # 打标签为 v1
|
||||
labels: {app-version: v1}
|
||||
- name: v2 # 打标签为 v2
|
||||
labels: {app-version: v2}
|
||||
|
||||
# Gateway: 入站流量入口
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: my-gateway
|
||||
spec:
|
||||
selector: {istio: ingressgateway}
|
||||
servers:
|
||||
- port: {number: 443, name: https, protocol: HTTPS}
|
||||
tls: {mode: SIMPLE, credentialName: my-cert}
|
||||
hosts: ["api.example.com"]
|
||||
```
|
||||
|
||||
### Istio 工作原理
|
||||
|
||||
```
|
||||
开发者视角:
|
||||
──────────────
|
||||
我的代码完全不知道 Service Mesh 的存在!
|
||||
不需要引入任何 SDK、注解或配置。
|
||||
|
||||
流量全部被 envoy sidecar 拦截和转发。
|
||||
|
||||
运维视角:
|
||||
──────────────
|
||||
Istiod (control plane) 统一管理所有 envoy proxy
|
||||
↓ 通过 xDS API (CDS/EDS/LDS/RDS)
|
||||
推送路由规则、监听配置、集群发现给每个 sidecar
|
||||
↓
|
||||
sidecar 热更新配置,无需重启应用
|
||||
```
|
||||
|
||||
```bash
|
||||
# 确认 sidecar 注入成功
|
||||
kubectl get pods -n default
|
||||
NAME READY STATUS
|
||||
my-service-abc123-xk9qp 2/2 Running ← 2/2 = app + envoy sidecar
|
||||
|
||||
# 检查 envoy 配置
|
||||
kubectl exec -it my-service-abc123-xk9qp -c envoy -- \
|
||||
envoy --admin-address-port localhost:15000 stats
|
||||
|
||||
# istioctl 诊断
|
||||
istioctl analyze # 分析配置正确性
|
||||
istioctl proxy-status # sidecar 同步状态
|
||||
istioctl proxy-config routes my-service-pod # 查看某 pod 的路由表
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/QUIC协议深度解析]] — QUIC 也是用户态实现的协议
|
||||
- [[hhs/NETWORK/WireGuardVPN原理与实践]] — WireGuard 同样在内核态运行
|
||||
- [[hhs/NETWORK/01-SocketAPI与backlog详解]] — Socket API 是 eBPF kprobe 最常挂钩的地方
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
tags: [计算机网络, WireGuard, VPN, ChaCha20, Curve25519, OpenVPN, IPsec]
|
||||
create time: 2026-05-18 05:35
|
||||
---
|
||||
|
||||
# WireGuard VPN 原理与实践
|
||||
|
||||
## 概述
|
||||
|
||||
WireGuard 是一个极简的 VPN 协议,代码量不到 OpenVPN 的 1/10、IPsec 的 1/25。它凭借极低的延迟和超高的吞吐量,正在成为新一代标准 VPN 方案——甚至已合入 Linux 内核(≥ 5.6)。
|
||||
|
||||
## 为什么比 OpenVPN / IPsec 快?
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
IPsec["IPsec\n• IKEv2 密钥交换 (多轮 RTT)\n• ESP/AH 扩展头链\n• 用户态+内核态来回跳转\n• ~10-20 种 cipher suite 协商"]
|
||||
|
||||
WG["WireGuard\n• Curve25519 密钥交换 (单次)\n• ChaCha20-Poly1305 AEAD\n• 纯内核态实现 (netlink API)\n• 仅 4 种密码学原语"]
|
||||
|
||||
style IPsec fill:#DDA0DD,color:#000
|
||||
style WG fill:#98FB98,color:#000
|
||||
```
|
||||
|
||||
### 性能对比实测数据
|
||||
|
||||
| 指标 | WireGuard | OpenVPN (UDP) | IPsec |
|
||||
|------|-----------|--------------|-------|
|
||||
| **吞吐量** | ~9.5 Gbps | ~1.5 Gbps | ~3.0 Gbps |
|
||||
| **CPU 占用 (Gbps)** | 0.8% | 12.5% | 3.2% |
|
||||
| **握手延迟** | < 1ms | 50-200ms | 100-300ms |
|
||||
| **代码行数** | ~4,000 LoC | ~50,000 LoC | ~100,000 LoC |
|
||||
| **配置复杂度** | 极简 (INI 格式) | 中等 | 复杂 |
|
||||
| **NAT 遍历** | 原生支持 | 需要辅助 (UDP encapsulation) | 复杂 |
|
||||
| **移动性** | 支持 (PersistentKeepalive + endpoint change) | 部分支持 | 有限支持 |
|
||||
|
||||
> [!tip] 代码行数少 = 更少的 bug = 更高的安全性
|
||||
>
|
||||
> 审计 ~4000 行 C 代码的成本远低于审计 ~50000 行。而且每少一行代码就少一个潜在的漏洞入口。
|
||||
|
||||
## WireGuard 的密码学基础
|
||||
|
||||
```
|
||||
WireGuard 仅使用 4 种经过严格审查的密码学原语:
|
||||
───────────────────────────────────────────────
|
||||
1. Curve25519 — ECDH 密钥交换 (x25519)
|
||||
2. ChaCha20 — 对称加密 (AES 替代)
|
||||
3. Poly1305 — MAC / AEAD 认证
|
||||
4. SHA-512 — 哈希函数
|
||||
|
||||
没有: RSA, ECC(除Curve25519外), CBC, HMAC, DRBG...
|
||||
→ 选择极少 → 无协商 → 更快更简单
|
||||
```
|
||||
|
||||
### 三重加密手风琴 (Triple Handshake)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Peer A
|
||||
participant B as Peer B
|
||||
|
||||
Note over A,B: 1. A → B: 主动握手 (A 发起)
|
||||
A->>B: {Ephemeral Key A} 用 B's Static Key 加密
|
||||
B->>A: {Ephemeral Key B} 用 A's Ephemeral Key + B's Static Key 加密
|
||||
A->>B: {Empty} 用 A's Ephemeral Key + B's Ephemeral Key 加密
|
||||
Note over A,B: ✅ 三方加密完成, 会话密钥建立
|
||||
|
||||
Note over A,B: 2. B → A: 响应式重握手 (B 轮换密钥)
|
||||
B->>A: {Ephemeral Key B'} 用 A's Static Key 加密
|
||||
A->>B: {Ephemeral Key A'} 用 B's Ephemeral Key + A's Static Key 加密
|
||||
B->>A: {Empty} 用 B's Ephemeral Key + A's Ephemeral Key 加密
|
||||
Note over A,B: ✅ 新会话密钥, 前向保密保证
|
||||
```
|
||||
|
||||
> [!question] 什么是前向保密 (Forward Secrecy)?
|
||||
> 即使长期密钥(Static Key)未来被泄露,也无法解密过去的通信——因为每次会话都使用了临时的 Ephemeral Key。三重手风琴保证了每一轮的临时密钥都在下一轮中被"销毁"。
|
||||
|
||||
## WireGuard 配置详解
|
||||
|
||||
### server.conf
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
Address = 10.0.0.1/24 # WireGuard 虚拟网卡 IP
|
||||
ListenPort = 51820 # UDP 监听端口
|
||||
PrivateKey = <server_private_key> # 私钥 (安全保存!)
|
||||
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # NAT 转发
|
||||
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
||||
|
||||
[Peer] # 客户端 1
|
||||
PublicKey = <client1_public_key>
|
||||
AllowedIPs = 10.0.0.2/32 # 只允许这个 IP 通过这个 peer
|
||||
PersistentKeepalive = 25 # 每 25s 发一次 keepalive (穿透 NAT)
|
||||
|
||||
[Peer] # 客户端 2
|
||||
PublicKey = <client2_public_key>
|
||||
AllowedIPs = 10.0.0.3/32
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
### client.conf
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
Address = 10.0.0.2/24 # 客户端自己的虚拟 IP
|
||||
PrivateKey = <client_private_key> # 客户端私钥
|
||||
|
||||
# DNS 解析通过隧道走 VPN
|
||||
DNS = 10.0.0.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = <server_public_key> # ⚠️ 注意方向: 客户端存服务端的公钥
|
||||
Endpoint = vpn.example.com:51820 # 服务器地址
|
||||
AllowedIPs = 0.0.0.0/0 # 所有流量走 VPN (全隧模式)
|
||||
# AllowedIPs = 10.0.0.0/24 # 仅内网流量走 VPN (-split tunneling)
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
### 一键生成密钥对
|
||||
|
||||
```bash
|
||||
# 生成服务器密钥对
|
||||
$ wg genkey | tee server-private.key | wg pubkey > server-public.key
|
||||
|
||||
# 生成客户端密钥对 (可重复 N 次)
|
||||
$ wg genkey | tee client1-private.key | wg pubkey > client1-public.key
|
||||
$ wg genkey | tee client2-private.key | wg pubkey > client2-public.key
|
||||
|
||||
# 生成 QR Code (方便手机扫码连接)
|
||||
$ qrencode -t UTF8 <(cat <(echo "[Interface]"); echo "PrivateKey=$(cat client1-private.key)"; echo "[Peer]"; echo "PublicKey=$(cat server-public.key)"; echo "AllowedIPs=0.0.0.0/0"; echo "Endpoint=vpn.example.com:51820"; echo "PersistentKeepalive=25")
|
||||
```
|
||||
|
||||
### 日常运维命令
|
||||
|
||||
```bash
|
||||
# 启动 WireGuard
|
||||
$ sudo wg-quick up wg0
|
||||
$ sudo systemctl enable --now wg-quick@wg0 # 开机自启
|
||||
|
||||
# 查看状态
|
||||
$ wg show
|
||||
interface: wg0
|
||||
public key: abc123def456...
|
||||
private key: (hidden)
|
||||
listening port: 51820
|
||||
|
||||
peer: xyz789ghi012...
|
||||
endpoint: 203.0.113.5:45678
|
||||
allowed ips: 10.0.0.2/32
|
||||
latest handshake: 3 seconds ago ← 3 秒前还在握手! 连接活跃
|
||||
transfer: 1.23 GiB received, 890 MiB sent
|
||||
|
||||
# 动态添加/移除 peer (不需要重启!)
|
||||
$ wg set wg0 peer <new_pubkey> allowed-ips 10.0.0.4/32
|
||||
$ wg remove wg0 peer <old_pubkey>
|
||||
|
||||
# 导入导出配置
|
||||
$ wg syncconf wg0 <(wg-quick strip wg0)
|
||||
```
|
||||
|
||||
## WireGuard 的限制与注意事项
|
||||
|
||||
```
|
||||
⚠️ WireGuard 不适合的场景:
|
||||
─────────────────────────────────
|
||||
• 需要传统用户认证 (PAP/CHAP/LDAP) → WireGuard 只认密钥
|
||||
• 细粒度 per-user 带宽控制 → 需配合 tc (traffic control)
|
||||
• 大规模动态节点 (千级以上) → 管理 1000 个 peer 的 AllowedIPs 很痛苦
|
||||
• 与现有 IPsec 设备互通 → WireGuard 不能做 IPsec gateway
|
||||
|
||||
✅ WireGuard 非常适合:
|
||||
─────────────────────────────────
|
||||
• 个人/小团队私有 VPN
|
||||
• 云服务器到 IDC 的专线
|
||||
• IoT 设备远程管理
|
||||
• Kubernetes 集群跨 AZ 通信
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/eBPF与ServiceMesh]] — eBPF 可用于监控 WireGuard 接口的流量
|
||||
- [[hhs/NETWORK/SDNSRv6与网络编程最佳实践]] — SDN + SRv6 是更宏大的网络可编程愿景
|
||||
- [[hhs/NETWORK/IPv6地址与扩展头部]] — IPv6 SLAAC 自动配置与 WireGuard 的静态分配对比
|
||||
@@ -0,0 +1,267 @@
|
||||
---
|
||||
tags: [计算机网络, SRv6, SDN, OpenFlow, P4, Go网络编程]
|
||||
create time: 2026-05-18 05:40
|
||||
---
|
||||
|
||||
# SDN、SRv6 与 Go 网络编程最佳实践
|
||||
|
||||
## 概述
|
||||
|
||||
当 eBPF 让内核可编程之后,SDN 让整个网络架构变得可编程。SRv6 将路由策略编码进 IPv6 地址本身,而 Source Routing 让源端可以精确控制数据包经过的每一跳。本章从原理走向工程实践。
|
||||
|
||||
## SRv6(Segment Routing over IPv6)
|
||||
|
||||
### 核心理念
|
||||
|
||||
SRv6 是 MPLS 的 IPv6 等价物,但有一个关键区别:**路径信息直接编码在 IPv6 地址中**。
|
||||
|
||||
```
|
||||
传统 MPLS: SRv6:
|
||||
标签栈 (stacked labels) IPv6 Ext Header: SRH (Segment Routing Header)
|
||||
每个中间节点查转发表 每个 Segment = 一个操作
|
||||
中间节点依赖 LSP 源端指定整条路径 (source routing)
|
||||
部署需要全局配置 LDP/RSVP-TE Controller 集中下发
|
||||
```
|
||||
|
||||
### SRv6 数据包结构
|
||||
|
||||
```
|
||||
IPv6 Header:
|
||||
Src: controller-assigned address
|
||||
Dst: SRH (Segment Routing Header)
|
||||
Next Header: 59 (NoNextHeader, 表示后面没有额外 header)
|
||||
Hdr Ext Len: N-1
|
||||
Routing Type: 4
|
||||
Segments Left: N
|
||||
Last Entry: N-1
|
||||
Flags: 0
|
||||
Tag: 0
|
||||
Segments[N]: endpoint6::action1 ← 第 N 段
|
||||
Segments[N-1]: endpoint6::action2 ← 第 N-1 段
|
||||
...
|
||||
Segments[1]: next-hop-ip ← 最后一段就是实际目的 IP
|
||||
Payload: actual data (TCP/UDP/ICMP)
|
||||
```
|
||||
|
||||
```
|
||||
SRv6 典型路径:
|
||||
──────────────────
|
||||
Client ──→ PE1 ──→ PE2 ──→ PE3 ──→ Provider Edge ──→ Destination
|
||||
↑ ↑
|
||||
SRH[2]=PE2 SRH[1]=PE3
|
||||
|
||||
数据包发出时 SRH = [PE2, PE3, Dest]
|
||||
经过 PE1 后: Segments Left=2, 目的地址变为 PE2::action
|
||||
经过 PE2 后: Segments Left=1, 目的地址变为 PE3::action
|
||||
到达 PE3: Segments Left=0, 交付给最终目的地
|
||||
```
|
||||
|
||||
```
|
||||
SRv6 的 End.SID 动作类型:
|
||||
├── End : 转发到目的地址 (最基本)
|
||||
├── End.X : 转发到指定邻接关系 (二层转发)
|
||||
├── End.DX : 解封装并三层转发 (VxLAN 出口)
|
||||
├── End.DT4 : 解封装并从 IPv4 VRF 转发
|
||||
├── End.DT6 : 解封装并从 IPv6 VRF 转发
|
||||
├── End.M : 添加到 MPLS 标签栈
|
||||
└── End.B6 : 绑定列表 + 执行 SRGB 操作
|
||||
```
|
||||
|
||||
## SDN(Software Defined Networking)
|
||||
|
||||
### SDN 三层架构
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph "Application Plane"
|
||||
App["Traffic Engineering<br/>Security Policies<br/>Multi-tenant Isolation<br/>Bandwidth On-Demand"]
|
||||
end
|
||||
|
||||
subgraph "Control Plane"
|
||||
Controller["SDN Controller<br/>OpenDaylight / ONOS / Ryu / FRRouting"]
|
||||
end
|
||||
|
||||
subgraph "Data Plane"
|
||||
SW1["Switch A<br/>OpenFlow Protocol"]
|
||||
SW2["Switch B<br/>OpenFlow Protocol"]
|
||||
SW3["Router C<br/>P4 可编程"]
|
||||
end
|
||||
|
||||
App -->|"REST API"| Controller
|
||||
Controller -->|"OpenFlow<br/>NetConf/YANG"| SW1
|
||||
Controller -->|"OpenFlow"| SW2
|
||||
Controller -->|"P4Runtime"| SW3
|
||||
|
||||
style Controller fill:#DDA0DD,color:#000
|
||||
style App fill:#98FB98,color:#000
|
||||
```
|
||||
|
||||
### OpenFlow 工作原理
|
||||
|
||||
```
|
||||
传统交换机: OpenFlow 交换机:
|
||||
数据面 + 控制面耦合 数据面与控制面分离
|
||||
每台设备自己算路由 控制器决定一切转发逻辑
|
||||
|
||||
主机收到包 → 本地查表 → 转发 主机收到包 → 查 Flow Table
|
||||
├─ 命中 → 按 Action 转发
|
||||
└─ 未命中 → Packet-In 问 Controller
|
||||
Controller → Flow-Mod 加规则
|
||||
↓
|
||||
下次命中 → 直接转发 (高速)
|
||||
```
|
||||
|
||||
```python
|
||||
# 用 ryu 框架写一个简单的 SDN Controller (Python)
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import MAIN_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryun.ofproto import ofproto_v1_3
|
||||
|
||||
class SimpleSDN(app_manager.RyuApp):
|
||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
||||
def packet_in_handler(self, ev):
|
||||
msg = ev.msg
|
||||
dp = msg.datapath
|
||||
ofp = dp.ofproto
|
||||
ofp_parser = dp.ofproto_parser
|
||||
|
||||
# 添加 FLOW_MOD: match 全部流量 → 泛洪到所有端口
|
||||
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
|
||||
|
||||
out = ofp_parser.OFPPacketOut(
|
||||
datapath=dp, buffer_id=msg.buffer_id,
|
||||
in_port=msg.match['in_port'], actions=actions
|
||||
)
|
||||
dp.send_msg(out)
|
||||
```
|
||||
|
||||
### P4 —— 可编程数据平面
|
||||
|
||||
```
|
||||
P4 (Programming Protocol-independent Packet Processors):
|
||||
允许你定义数据包的解析器和处理流水线,然后编译成
|
||||
特定 ASIC/FPGA/NIC 上的硬件代码。
|
||||
|
||||
核心概念:
|
||||
parser → 如何解析报文头
|
||||
deparser → 如何组装报文
|
||||
pipeline → 匹配- action 表的流处理逻辑
|
||||
control → 整个管道的编排
|
||||
```
|
||||
|
||||
```p4
|
||||
// P4 示例: 简单的负载均衡器
|
||||
header ethernet_t ethernet;
|
||||
header ipv4_t ipv4;
|
||||
|
||||
struct headers {
|
||||
ethernet_t ethernet;
|
||||
ipv4_t ipv4;
|
||||
}
|
||||
|
||||
parser parse_headers(packet_in& p, out headers_t hdr) {
|
||||
p.extract(hdr.ethernet);
|
||||
p.extract(hdr.ipv4);
|
||||
}
|
||||
|
||||
control ingress(packet_in& p, inout headers_t hdr) {
|
||||
// 基于目标 IP 选择后端
|
||||
action forward_to_server1() {
|
||||
modify_field(hdr.ipv4.dstAddr, SERVER1_IP);
|
||||
}
|
||||
action forward_to_server2() {
|
||||
modify_field(hdr.ipv4.dstAddr, SERVER2_IP);
|
||||
}
|
||||
|
||||
table lb_table {
|
||||
key = {
|
||||
hdr.ipv4.dstAddr: exact;
|
||||
}
|
||||
actions = { forward_to_server1; forward_to_server2; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Go 中的网络编程最佳实践汇总
|
||||
|
||||
### 高性能 HTTP Server 模板
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewServer(addr string, handler http.Handler) *http.Server {
|
||||
return &http.Server{
|
||||
Addr: addr,
|
||||
Handler: handler,
|
||||
|
||||
// 超时设置 (防 Slowloris)
|
||||
ReadTimeout: 10 * time.Second,
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20, // 1MB
|
||||
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StartListener 用自定义 listener 启动
|
||||
func StartListener(srv *http.Server, network, addr string) error {
|
||||
ln, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 底层使用 SO_REUSEPORT 实现多 worker 共享端口
|
||||
return srv.Serve(ln)
|
||||
}
|
||||
```
|
||||
|
||||
### 出站请求的高性能 Transport
|
||||
|
||||
```go
|
||||
import "golang.org/x/net/http2"
|
||||
|
||||
func HighPerfTransport() *http.Transport {
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: 200,
|
||||
MaxIdleConnsPerHost: 50, // ← Go 默认只有 2, 务必显式设置!
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true, // RFC 6724 happy eyeballs
|
||||
}).DialContext,
|
||||
}
|
||||
|
||||
// 强制 HTTP/2 (Go 1.6+ 自动协商, 显式配置确保)
|
||||
http2.ConfigureTransport(transport)
|
||||
|
||||
return transport
|
||||
}
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/WireGuardVPN原理与实践]] — WireGuard 也可以作为 SDN 的数据平面组件
|
||||
- [[hhs/NETWORK/eBPF与ServiceMesh]] — eBPF 可以与 SDN Controller 协同工作
|
||||
- [[hhs/NETWORK/03-零拷贝与GoNetpoller]] — Go 网络编程的基础技术
|
||||
Reference in New Issue
Block a user