【WireGuard】内核代码路径:从 wg_xmit 到加解密 worker

💡 原文中文,约5000字,阅读约需12分钟。
📝

内容提要

本文介绍Linux 6.6内核中WireGuard的实现,聚焦发送路径(wg_xmit经AllowedIPs查找选peer)、加解密worker并行队列、协议常量(如REKEY_AFTER_TIME)、握手入口及Generic Netlink配置面。强调cryptokey routing用同一trie按目的/源地址选peer,并给出源码阅读顺序,对比用户态wireguard-go,突出内核热路径避免拷贝的优势。

🔎

延伸解读

阅读源码的锚点:版本与行号

文章强调所有代码引用均基于 Linux 6.6 LTS,并给出各核心文件的行数。这提醒读者,内核代码随版本演进,行号可能变化。阅读时应以版本标签为准,避免因行号漂移导致误解。建议在阅读时对照当前内核源码,并关注函数名而非绝对行号。

cryptokey routing 的代码形态

文章指出 cryptokey routing 在代码中体现为同一张 trie 的双向查找:发送时用目的地址(wg_allowedips_lookup_dst),接收时用源地址(wg_allowedips_lookup_src)。这解释了 WireGuard 如何仅凭 IP 地址选择 peer,无需传统 SPD/SAD 分离。理解这一点有助于把握 WireGuard 简化设计的核心。

并行 worker 与保序的权衡

文章提到加解密 worker 可并行执行以利用多核,但同一 peer 的包序由 per-peer 队列约束。这揭示了 WireGuard 在性能与顺序保证之间的设计取舍:并行提升吞吐,队列确保会话层不乱序。读者可据此理解为何 WireGuard 在高负载下仍能保持稳定。

协议常量:运维与调试的关键

文章列出 messages.h 中的关键常量,如 REKEY_AFTER_TIME=120、REJECT_AFTER_TIME=180 等。这些常量直接影响密钥轮换和会话寿命,运维人员可通过调整这些值优化安全性与性能。但文章提醒,具体行为以 timers.c 中的注释和 mod_peer_timer 调用点为准,避免凭记忆换算。

Q&A

WireGuard 内核模块中,发送数据包时如何根据目的 IP 选择 peer?

在 wg_xmit 函数中,通过调用 wg_allowedips_lookup_dst 查找 AllowedIPs 表(trie),根据内层 IP 数据包的目的地址选择对应的 peer。如果查找失败,则返回 -ENOKEY 并发送 ICMP 错误。

WireGuard 内核实现中,加解密 worker 是如何并行处理数据包的?

WireGuard 在接口创建时初始化 encrypt_queue 和 decrypt_queue,分别对应 wg_packet_encrypt_worker 和 wg_packet_decrypt_worker。这些 worker 可以并行运行,利用多核 CPU 提高 ChaCha20 加解密吞吐量。同时,每个 peer 的队列保证了数据包顺序,避免乱序。

WireGuard 协议中,REKEY_AFTER_TIME 和 REJECT_AFTER_TIME 分别代表什么?

REKEY_AFTER_TIME 是 120 秒,表示密钥使用超过该时间后应触发重新握手;REJECT_AFTER_TIME 是 180 秒,表示密钥超过该时间后将被拒绝使用。这些常量定义在 messages.h 中,用于控制密钥轮换和过期。

WireGuard 的握手消息类型有哪些?

WireGuard 定义了四种消息类型:MESSAGE_HANDSHAKE_INITIATION (1)、MESSAGE_HANDSHAKE_RESPONSE (2)、MESSAGE_HANDSHAKE_COOKIE (3) 和 MESSAGE_DATA (4)。这些类型在 messages.h 中定义,用于区分不同的协议消息。

WireGuard 内核模块中,配置面是如何实现的?

WireGuard 使用 Generic Netlink 作为配置面,通过 netlink.c 文件实现。用户态工具 wg(8) 通过 Generic Netlink 与内核通信,配置接口私钥、监听端口、peer 公钥、PSK、endpoint、AllowedIPs 和 keepalive 间隔等参数。

WireGuard 内核实现与用户态 wireguard-go 的主要区别是什么?

内核实现将热路径(数据包发送、接收、加解密)放在内核中,避免了用户态拷贝,性能更高;而 wireguard-go 是用户态实现,便于在非 Linux 或无法加载内核模块的环境中使用。两者协议一致,但队列和 NAPI 等细节以内核 C 实现为准。

WireGuard 的 cryptokey routing 是如何工作的?

cryptokey routing 使用同一张 AllowedIPs trie 进行双向查找:发送时根据目的 IP 查找 peer(wg_allowedips_lookup_dst),接收时根据源 IP 查找 peer(wg_allowedips_lookup_src)。这样将路由和加密密钥绑定在一起,简化了设计。

WireGuard 内核代码中,建议的源码阅读顺序是什么?

建议按以下顺序阅读:先看 messages.h 了解常量和消息结构;然后看 device.c 的 wg_xmit 和 allowedips.c 理解 cryptokey routing;接着看 send.c 和 receive.c 的 worker 入口;再看 noise.c 的握手创建和消费;之后是 timers.c 和 cookie.c;最后看 netlink.c 了解配置如何进入内核。

🏷️

标签

➡️

继续阅读