IPsec 的配置需要理解 IKE、SPD、SAD、策略与提案协商的层层嵌套,OpenVPN 则把 TLS 控制通道和一堆可调参数塞进同一个进程——两者都能用,但都「难配、难审、难排错」。WireGuard 反其道而行:整个实现只有约四千行内核代码,密码学套件固定不可协商,配置就是几段 [Peer],却能做到更快的握手与更高的吞吐。
本文从 WireGuard 的设计哲学讲起,拆解 Noise_IK 握手与固定密码学套件的选择,然后重点解释 Cryptokey Routing 里 AllowedIPs 的双重语义,最后落到 wg-quick 实战配置、性能对比与多点对多点组网。
一、从 IPsec/OpenVPN 到 WireGuard
1.1 设计哲学:少即是多
WireGuard 的核心判断是:绝大多数 VPN 部署只用得到一套「现代且安全」的密码学,与其提供几十种可协商组合让人配错,不如把组合固定死。这带来三个直接结果:代码量小到可审计、没有降级攻击面、握手极快。
WireGuard 与前辈的对比:
IPsec/IKEv2 : 协议栈庞大,策略与提案组合爆炸,互操作性差
OpenVPN : 用户态,TLS 控制通道,配置项繁多,吞吐受限于实现
WireGuard : 内核态,固定密码学,约 4000 行,无算法协商
1.2 固定的密码学套件
WireGuard 不允许协商算法,密码学原语在编译期确定。这些原语都是现代、抗侧信道、且在常见 CPU 上有加速实现的。
| 用途 | 算法 | 说明 |
|---|---|---|
| 密钥交换 | Curve25519 | ECDH,256 位,抗侧信道 |
| 对称加密 | ChaCha20-Poly1305 | AEAD,无 AES-NI 也快 |
| 哈希 | BLAKE2s | 比 SHA 更快,用于握手哈希 |
| 握手 MAC | HMAC-BLAKE2s / keyed-BLAKE2s | 认证与密钥派生 |
| 密钥派生 | HKDF | 从握手结果派生会话密钥 |
二、Noise 协议框架与握手
2.1 Noise_IK 握手模式
WireGuard 的握手基于 Noise Protocol Framework 的 IK 模式:发起方(I)的静态公钥对响应方(R)已知,响应方的静态公钥对发起方已知。一次握手只有两个报文,即可完成双向认证并派生出会话密钥。
Noise_IK 握手(两报文):
发起方 Initiator 响应方 Responder
------------------ -----------------
Handshake Initiation
+ 加密的静态公钥 (I 的身份)
+ 时间戳(抗重放)
+ 临时公钥 ephemeral
-------------->
Handshake Response
+ 响应方临时公钥
+ 加密的空载荷
<--------------
握手完成 → 双方派生:
- 一对传输密钥(发送/接收各一)
- 用于 Cookie 抗 DoS 的密钥
2.2 抗重放与 Cookie 机制
WireGuard 用单调递增的时间戳抗重放:握手报文里带 TAI64N 时间戳,响应方拒绝「不新于上次」的握手。面对 DoS 攻击(伪造源 IP 发起大量握手),WireGuard 可选启用 Cookie:响应方先要求对方回一个基于其 IP 计算的 Cookie,验证通过才做昂贵的 Curve25519 运算。
Cookie 抗 DoS 流程:
1. 攻击者伪造源 IP 发送握手请求
2. 响应方不回真实响应,而是回 Cookie Reply
(MAC = keyed-BLAKE2s(源 IP, 服务端密钥))
3. 攻击者必须用正确源 IP 收到 Cookie 才能继续
4. 伪造源 IP 收不到 → 攻击失效
2.3 密钥的静默轮换
会话密钥不是固定的:WireGuard 每约 120 秒(REKEY_AFTER_TIME)主动发起新握手轮换密钥,并在约 180 秒(REJECT_AFTER_TIME)后拒绝使用旧密钥。轮换是静默的,应用层无感知,实现了前向保密与密钥新鲜度。
| 参数 | 默认值 | 含义 |
|---|---|---|
| REKEY_AFTER_TIME | 120s | 主动重新握手 |
| REKEY_ATTEMPT_TIME | 90s | 重试握手的最长时间 |
| REJECT_AFTER_TIME | 180s | 拒绝旧密钥的截止 |
| REKEY_AFTER_MESSAGES | 2^60 | 按报文数触发轮换 |
三、Cryptokey Routing 与密钥管理
3.1 AllowedIPs 的双重语义
AllowedIPs 是 WireGuard 最容易被误解的配置项,它同时承担两个职责:出方向的路由选择(哪些目标 IP 走这个 Peer)和入方向的访问控制(只接受源 IP 落在列表内的包)。一个 Peer 的 AllowedIPs 写错,往往表现为「能发不能收」或「能收不能发」。
AllowedIPs 双重语义:
出方向(路由):本机发出的包,目标 IP 匹配某 Peer 的 AllowedIPs
→ 加密后发给该 Peer
入方向(过滤):从某 Peer 收到的包,源 IP 必须落在其 AllowedIPs
→ 否则丢弃(防源地址伪造)
例:AllowedIPs = 10.0.0.2/32, fd00::2/128
- 发往 10.0.0.2 的包走这个 Peer
- 只接受来自 10.0.0.2 的包
3.2 密钥对与公钥交换
每个节点持有一对 Curve25519 密钥:私钥本地保密,公钥可自由分发。WireGuard 本身没有 PKI、没有证书、没有 CA——它靠「配置里写上对方的公钥」建立信任,这让密钥分发退化为简单的配置管理问题。
# 生成密钥对
wg genkey | tee privatekey | wg pubkey > publickey
chmod 600 privatekey
# 从已有私钥推导公钥
wg pubkey < privatekey
# 生成预共享密钥(可选,叠加后量子对称加固)
wg genpsk > preshared.key
3.3 预共享密钥的用途
除公钥外,WireGuard 支持可选的预共享密钥(PSK)。它不替代公钥握手,而是叠加在握手结果之上——即使未来 Curve25519 被量子计算机攻破,PSK 仍提供一层对称加固。这是「现在先布防、以后好升级」的低成本做法。
四、性能与对比
4.1 吞吐与延迟
WireGuard 在内核态处理数据包,且密码学原语都有高效实现,因此吞吐远高于用户态的 OpenVPN。在常见 x86 服务器上,单条 WireGuard 隧道可跑满多 Gbps,接近线速;OpenVPN 往往卡在数百 Mbps。
| 维度 | WireGuard | OpenVPN | IPsec/IKEv2 |
|---|---|---|---|
| 运行位置 | 内核态 | 用户态 | 内核态 |
| 代码规模 | ~4000 行 | 十万行级 | 庞大 |
| 密码学协商 | 无,固定 | 可协商 | 可协商 |
| 握手耗时 | 毫秒级 | 数十毫秒 | 毫秒级 |
| 典型吞吐 | 多 Gbps | 数百 Mbps | 多 Gbps |
| 移动漫游 | 原生支持 | 需重连 | 支持 |
| NAT 穿透 | UDP + 保持 | UDP/TCP | UDP |
4.2 漫游与 NAT 保持
WireGuard 的 Peer 是「无连接」的:只要收到来自某个 IP:端口 的合法(能解密)握手,就更新该 Peer 的端点。手机从 Wi-Fi 切到 4G、公网 IP 变化,都无需重新配置——这被称为「被动漫游」。为保证 NAT 映射不过期,需要 PersistentKeepalive。
漫游机制:
- Peer 端点不是固定的,而是「最近一次成功解密报文的来源」
- IP 变化后,只要对端能再次发来合法报文,端点自动更新
- NAT 后的节点需 PersistentKeepalive = 25 秒保持映射
注意:只有一端主动发包,NAT 映射才会刷新
→ 位于 NAT 后的 Peer 设 keepalive,公网侧可不设
五、实战配置
5.1 最简单的点对点
用 wg-quick 配合配置文件,可以在几行内建起一条点对点隧道。配置分 [Interface] 与 [Peer] 两段。
# /etc/wireguard/wg0.conf —— 节点 A(10.0.0.1)
[Interface]
PrivateKey = <A 的私钥>
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
# 节点 B
PublicKey = <B 的公钥>
AllowedIPs = 10.0.0.2/32
Endpoint = 203.0.113.2:51820
PersistentKeepalive = 25
# 启停隧道
wg-quick up wg0
wg-quick down wg0
# 查看当前状态(握手时间、收发字节)
wg show
# 动态添加 Peer(无需重启)
wg set wg0 peer <公钥> allowed-ips 10.0.0.3/32 endpoint 203.0.113.3:51820
5.2 全流量 VPN(网关模式)
要让客户端所有流量都走 VPN,把 AllowedIPs 设为 0.0.0.0/0, ::/0,并在服务端开启转发与 NAT。注意此时客户端配置里要写 DNS,否则仍用本地解析。
# 客户端:全流量走 VPN
[Peer]
PublicKey = <服务端公钥>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = vpn.example.com:51820
PersistentKeepalive = 25
DNS = 10.0.0.1
# 服务端开启转发与 NAT
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -j ACCEPT
5.3 多点对多点(Hub-and-Spoke 与 Full Mesh)
中心辐射模式下,所有节点只与 Hub 建隧道,Hub 负责转发;AllowedIPs 在 Hub 上列出所有 Spoke 的地址。Full Mesh 则每个节点互相配置 Peer,无中心节点,延迟最低但配置量为 O(N²)。
Hub-and-Spoke:
Hub AllowedIPs 含所有 Spoke 网段 → 负责路由转发
优点:配置集中、易管控;缺点:Hub 是瓶颈与单点
Full Mesh:
每个节点为其他所有节点配 [Peer]
优点:无中心、延迟最低;缺点:配置 O(N²)、密钥分发复杂
5.4 常见故障对照
| 现象 | 原因 | 排查 |
|---|---|---|
| 能握手无流量 | AllowedIPs 不含对端源地址 | wg show 看 latest handshake |
| 单向不通 | 一端未设 keepalive,NAT 映射过期 | 在 NAT 后节点设 PersistentKeepalive |
| 大包超时 | MTU 未下调 | 设 MTU = 1420 或更小 |
| 握手不更新 | 时钟不同步 | 校时,检查时间戳 |
| 全流量不通 | 未开转发或 NAT | sysctl net.ipv4.ip_forward、iptables 规则 |
# 探测隧道 MTU(外层 UDP 隧道需预留开销)
ping -M do -s 1400 -c 1 10.0.0.2
# 建议:WireGuard 默认 MTU 1420,IPv6 或额外封装时下调
5.5 密钥轮换与配置自动化
WireGuard 没有 CA,密钥分发就是「把公钥填进配置」。节点一多,手工维护 [Peer] 会迅速失控,必须自动化:把密钥与 Peer 列表放进配置管理或 KV 存储,用模板渲染出 wg0.conf。
# 用 wg 动态增删 Peer,无需重启接口
wg set wg0 peer <公钥> allowed-ips 10.0.0.9/32
wg set wg0 peer <公钥> remove
# 导出当前运行配置(含运行时端点),用于备份或迁移
wg showconf wg0
# 用 systemd 管理持久化
systemctl enable wg-quick@wg0
密钥轮换策略:
- 每个节点独立密钥对,绝不共享私钥
- 私钥权限 600,仅 root 可读
- 定期轮换(如 90 天),先加新公钥再删旧公钥
- PSK 可选,逐 Peer 配置,作为后量子加固
5.6 与其他方案的组合
WireGuard 只解决「点对点加密隧道」,它不做路由分发、不做服务发现。生产组网通常把 WireGuard 与路由协议或编排系统组合:用 BGP 在隧道之上分发网段,或用编排工具统一下发 Peer 配置。
| 组合 | 作用 | 场景 |
|---|---|---|
| WireGuard + BGP | 隧道上跑动态路由 | 多站点互联、自动收敛 |
| WireGuard + systemd-networkd | 声明式配置 | 服务器自动化 |
| WireGuard + 配置管理 | 模板化 Peer 分发 | 大规模节点 |
| WireGuard + 监控 | 采集 wg show 指标 | 隧道健康与流量审计 |
# 采集隧道健康指标(握手时间、收发字节、丢包)
wg show all dump | awk '{print $1, $5, $6, $7, $8}'
# 若 latest-handshake 长期不更新,说明隧道已断
5.7 MTU 与分片排错
WireGuard 走 UDP,封装开销不小:外层 IP(20/40)+ UDP(8)+ WireGuard 头(32)= 60~80 字节。默认 MTU 1420 是为 IPv4 场景预留的保守值,IPv6 或叠加其他隧道时需要再下调,否则会出现「握手正常、传输卡死」。
MTU 计算(IPv4 外层):
1500 - 20(IP) - 8(UDP) - 32(WG) = 1440
wg-quick 默认取 1420(留安全余量)
叠加隧道(如 WG over WG):
1420 - 60 = 1360,需逐层下调
# 逐字节探测隧道真实 MTU(-M do 禁止分片)
for s in 1400 1380 1360 1340; do
ping -M do -s $s -c 1 -W 1 10.0.0.2 >/dev/null 2>&1 \
&& echo "MTU ok: $((s+28))" && break
done
六、小结
| 主题 | 核心要点 | 落地建议 |
|---|---|---|
| 设计 | 固定密码学、内核态、无协商 | 生产直接上,无需调算法 |
| 握手 | Noise_IK 两报文,抗重放 | 保证时钟同步 |
| 密钥 | Curve25519 密钥对 + 可选 PSK | PSK 作为后量子加固 |
| 路由 | AllowedIPs 兼路由与 ACL | 出入双向都要覆盖对端地址 |
| 漫游 | 端点随合法报文自动更新 | NAT 后节点设 keepalive |
| 性能 | 内核态,多 Gbps | 注意 MTU 与转发规则 |
WireGuard 的价值在于把 VPN 从「一门需要考证的技术」变成「一段可以看懂配置」。它用固定的现代密码学和极小的代码量换来可审计性与性能,用 AllowedIPs 一个字段同时表达路由与访问控制。想理解它在更大网络里的位置,可以对照 网络安全与加密通信
里的密钥与认证体系、零信任架构
中「网络即边界」的思路,以及 IP 路由与转发
里 AllowedIPs 背后的路由表语义。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。