【WireGuard】内核代码路径:从
wg_xmit 到加解密 worker
协议纸面落进 Linux 的位置是
drivers/net/wireguard/。本文锚定 Linux
6.6 LTS 源码(从 torvalds/linux 的
v6.6
树核对),按发送、接收、握手、配置四条线把关键函数钉住。全景叙事与更多
Mermaid 总图见已有 WireGuard
内部实现;本文补带行号的阅读地图与 6.6
常量。
一、目录与体量(Linux 6.6)
对 v6.6 标签下若干核心 .c
的行数(wc -l,本机拉取核对):
| 文件 | 行数 | 职责 |
|---|---|---|
noise.c |
861 | 握手、密钥派生、keypair |
netlink.c |
644 | Generic Netlink 配置 |
receive.c |
586 | UDP 接收、解密路径、NAPI |
device.c |
479 | net_device、wg_xmit |
send.c |
413 | 发送、加密 worker、握手发送 |
allowedips.c |
389 | IPv4/IPv6 trie |
timers.c |
243 | rekey / keepalive / zeroing |
cookie.c |
236 | MAC1/MAC2 / cookie reply |
另有
socket.c、peer.c、ratelimiter.c、queueing.*
等。体量仍然远小于完整 IPsec+IKE
栈;行数会随主线演进,引用时写清版本。
二、发送:ndo_start_xmit
→ AllowedIPs → 队列
device.c 把 ndo_start_xmit 指到
wg_xmit(约第 239 行
.ndo_start_xmit = wg_xmit)。
wg_xmit(约第 137 行起)核心逻辑:
- 校验内层是 IP/IPv6(否则
-EPROTONOSUPPORT)。 peer = wg_allowedips_lookup_dst(&wg->peer_allowedips, skb)—— 目的 IP 选 peer。- 无 peer →
-ENOKEY,并对 IPv4/IPv6 打 debug,走 ICMP 错误路径(err_icmp)。 - endpoint 未配置且从未学到 →
-EDESTADDRREQ。 - 处理 GSO 分段后,把 skb
交给后续发送/加密路径(
send.c)。
AllowedIPs 查找(allowedips.c 约 356–372
行):
// Linux 6.6 drivers/net/wireguard/allowedips.c
struct wg_peer *wg_allowedips_lookup_dst(struct allowedips *table,
struct sk_buff *skb)
{
if (skb->protocol == htons(ETH_P_IP))
return lookup(table->root4, 32, &ip_hdr(skb)->daddr);
else if (skb->protocol == htons(ETH_P_IPV6))
return lookup(table->root6, 128, &ipv6_hdr(skb)->daddr);
return NULL;
}入方向对称函数是
wg_allowedips_lookup_src(看内层源地址)。这就是
cryptokey routing 在代码里的形状:同一张 trie,出看
dst、进看 src。
flowchart TD
A["IP stack -> wg0"] --> B["wg_xmit"]
B --> C["wg_allowedips_lookup_dst"]
C -->|miss| D["-ENOKEY / ICMP"]
C -->|hit| E["peer staged queue"]
E --> F["encrypt_queue worker"]
F --> G["UDP to endpoint"]
三、加密 / 解密 worker 与保序
device.c 在接口创建时初始化并行队列(约
353–358 行):
encrypt_queue→wg_packet_encrypt_worker(定义在send.c约 287 行)decrypt_queue→wg_packet_decrypt_worker(定义在receive.c约 493 行)
声明见 queueing.h。设计意图(与 NDSS
论文系统部分、站内 89 文一致):
- 加解密可并行(多核 worker),吃满 ChaCha20 吞吐。
- 同一 peer 的包序由 per-peer 队列约束,避免会话层乱序放大。
send.c 中在发送路径检查
REKEY_AFTER_MESSAGES、密钥出生时间等,决定是否发起新握手(约
132 行附近、365 行附近对 REJECT_AFTER_TIME
的引用)。具体阈值常量见下一节。
四、messages.h:协议常量的内核锚点
Linux 6.6 messages.h
中与运维/协议相关的枚举(节选):
enum limits {
REKEY_AFTER_MESSAGES = 1ULL << 60,
REJECT_AFTER_MESSAGES = U64_MAX - COUNTER_WINDOW_SIZE - 1,
REKEY_TIMEOUT = 5,
REKEY_AFTER_TIME = 120,
REJECT_AFTER_TIME = 180,
KEEPALIVE_TIMEOUT = 10,
/* ... */
};
enum message_type {
MESSAGE_HANDSHAKE_INITIATION = 1,
MESSAGE_HANDSHAKE_RESPONSE = 2,
MESSAGE_HANDSHAKE_COOKIE = 3,
MESSAGE_DATA = 4
};timers.c 文件头注释用这些名字描述
keepalive、拒绝寿命、临时密钥清零(REJECT_AFTER_TIME * 3)等行为——读定时器时以注释
+ mod_peer_timer
调用点为准,不要凭记忆换算。
Cookie
相关:COOKIE_SECRET_MAX_AGE = 2 * 60,与官方「两分钟轮换
cookie secret」一致。
五、握手代码入口
创建 initiation:noise.c 中
wg_noise_handshake_create_initiation(约第 517
行)。
send.c 约第 34
行在需要时调用它组装包并送出。
接收路径在 receive.c 分发 type
1/2/3/4:握手进 noise 状态机;data 进解密与
counter 校验;负载下若需要则走
cookie.c。站内 89 文对 initiation/response
的逐步注释更细,本文不重复公式——协议公式以 协议篇 与官方
protocol 页为准。
三密钥对轮换(current / previous / next)在
noise.c 的 keypair 管理里;寿命与
REJECT_AFTER_TIME、REKEY_AFTER_TIME
联动。读代码时抓住:新旧密钥短暂共存是为了 UDP
乱序与 rekey 竞态,不是长期双会话。
六、配置面:Generic Netlink
用户态 wg(8) 不直接 ioctl 私有结构,而是通过
Generic Netlink 与 netlink.c 对话(Linux 6.6
该文件约 644 行)。配置项映射到:
- 接口私钥、listen port
- peer 公钥、PSK、endpoint、AllowedIPs、keepalive 间隔
这解释了为何 wg show / wg set
与 ip link 分工清晰:ip
管地址与路由,wg 管 cryptokey routing
表。运维篇用 netns 实验展示二者必须同时正确。
七、和用户态实现的关系
wireguard-go:官方用户态实现,便于非 Linux 或无内核模块环境。
- 内核模块:热路径在内核,避免 TUN 用户态拷贝(NDSS §I 对 OpenVPN 的批评点)。
读内核代码时不要假设 Go 实现行号对应;协议必须一致,队列与 NAPI 细节以 C 模块为准。
八、建议阅读顺序(源码)
messages.h—— 常量与消息结构
device.c::wg_xmit+allowedips.c—— cryptokey routing
send.c/receive.c的 worker 入口 —— 数据面
noise.c握手创建/消费 —— 控制面
timers.c+cookie.c—— 寿命与 DoS
netlink.c—— 配置如何进内核
读完后回到 内部实现文
第三节对照总图,或进入 运维篇 看这些路径在
wg show 里暴露为什么字段。
参考资料
源码
- Linux 6.6
drivers/net/wireguard/:device.c、allowedips.c、send.c、receive.c、noise.c、messages.h、timers.c、netlink.c、queueing.h
论文 / 官方
- Donenfeld, J. A. NDSS 2017(系统实现与队列讨论见论文后部章节)。
- https://www.wireguard.com/protocol/
站内
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】WireGuard 内部实现:Cryptokey Routing、Noise IK 握手与内核数据路径
WireGuard 用不到 4000 行内核代码替代了 IPSec/IKE 数十万行的协议栈——这不是少写功能,而是用固定密码学原语、固定握手模式和 cryptokey routing 这三个设计决定,换掉了证书体系、协商机和 SPD/SAD 分离。本文钻进源码和协议握手过程,逐层拆解 cryptokey routing 的双向执行、Noise IKpsk2 的 1.5 RTT 握手(含四次 ECDH 的安全属性贡献)、内核多核队列架构、timer 状态机、cookie DoS 防御和三密钥对轮换。
【WireGuard】使用与运维:netns 实测、AllowedIPs 与故障模式
在 WSL2 Linux 6.6 上用双 netns + veth 实测 WireGuard 握手与 ping;整理 wg/wg-quick 工作流、AllowedIPs 与 FIB 脱节、漫游、MTU 与明确不适用场景。
【WireGuard】深度探讨:形式化证明、密码敏捷性与后量子
Dowling–Paterson 对 1.5 RTT confirmation 的证明障碍、Donenfeld 的回应、netdev 上「无算法协商」争论、PSK 量子权宜之计,以及 WireGuard 作为 ZTNA 数据面的边界与开放问题。
【WireGuard】设计哲学:故意的分层破坏与密码学观点
WireGuard 不是把 IPSec 写短一点。NDSS 2017 论文把「正确分层」当成复杂度的来源,用 cryptokey routing、固定原语与带外密钥分发换可审计实现。本文钉住这些设计决定及其边界。