资讯动态

windows 驱动实例分析系列: wireguard-nt驱动分析-driver篇(二)

发布时间:2026/8/22 12:23:27 来源:尧图企业网站定制
WireGuardNT 驱动代码解析 - 第二篇加密、Noise 协议与数据包收发路径1. 概述本文为驱动代码解析的第二篇聚焦于 WireGuardNT 的核心数据面处理逻辑涵盖加密原语、Noise 协议握手、数据包加解密、队列调度以及 UDP 套接字通信。这些模块共同实现了 VPN 隧道的安全数据传输。涉及的主要源文件crypto.c/crypto.h– 加密算法实现ChaCha20-Poly1305、BLAKE2s、SipHash、Curve25519。noise.c/noise.h– Noise_IKpsk2 握手协议静态身份管理密钥派生与轮换。messages.h– 定义 WireGuard 消息格式握手、数据、Cookie。send.c– 数据包发送路径加密、排队、传输。receive.c– 数据包接收路径解密、验证、路由。socket.c/socket.h– WSK 套接字管理端点解析UDP 收发。queueing.c/queueing.h– 多核工作队列PREV_QUEUE 并发队列。timers.c/timers.h– 定时器管理重传、保活、密钥过期。2. 加密原语crypto.c / crypto.hcrypto.c实现了 WireGuard 所需的所有密码学操作并针对 x86/x64 平台提供 SSSE3、AVX、AVX2、AVX512 优化版本。代码结构如下2.1 CPU 特性检测CryptoDriverEntry在驱动初始化时通过 CPUID 指令检测支持的指令集并禁用不存在的特性例如 Skylake-X 上禁用 AVX512F 以避免降频。SimdGet和SimdPut使用KeSaveExtendedProcessorState保存/恢复 AVX 寄存器状态确保加密操作不会破坏用户态浮点上下文。2.2 ChaCha20 流密码ChaCha20Init初始化上下文支持 64 位 nonce。核心函数ChaCha20根据SIMD_STATE选择实现ChaCha20AVX512、ChaCha20AVX512VL、ChaCha20AVX2、ChaCha20SSSE3或纯 C 的ChaCha20ALU。HChaCha20用于 XChaCha20 的密钥派生将 24 字节 nonce 派生为 32 字节子密钥和 64 位 nonce。2.3 Poly1305 消息认证码支持多种内部表示Base 2^64、Base 2^26、Base 2^44以适应不同 SIMD 实现。Poly1305Init、Poly1305Update、Poly1305Final提供标准 API底层分派到Poly1305InitCore、Poly1305BlocksCore、Poly1305EmitCore根据 CPU 特性选择 AVX512IFMA、AVX2、AVX 或纯 C。2.4 ChaCha20-Poly1305 AEADChaCha20Poly1305Encrypt/Decrypt处理常规内存缓冲区。ChaCha20Poly1305EncryptMdl/DecryptMdl直接操作 MDL 链避免额外拷贝用于 NDIS 数据包。XChaCha20Poly1305Encrypt/Decrypt使用 24 字节 nonce用于 Cookie。2.5 BLAKE2s 哈希提供Blake2sInit、Blake2sUpdate、Blake2sFinal支持密钥初始化HMAC-like。Blake2s单次调用函数以及Blake2s256Hmac用于 HKDF。2.6 SipHash 和 HalfSipHash用于哈希表查找对等体公钥哈希和索引哈希。Siphash系列函数64 位输出和Hsiphash系列32 位输出用于指针大小优化。2.7 Curve25519 椭圆曲线完整实现了 x25519 标量乘法基于 fiat-crypto 形式化验证代码由 Coq 生成。Curve25519执行标量乘法输出共享密钥。Curve25519GeneratePublic利用基点 9 生成公钥。Curve25519ClampSecret对私钥进行位掩码清除低三位置高位。所有加密函数均使用常量时间比较CryptoEqualMemory16/32、CryptoIsZero32以防止侧信道攻击。3. Noise 协议实现noise.c / noise.hNoise_IKpsk2 是 WireGuard 的握手协议用于建立对等体之间的会话密钥。3.1 静态身份NOISE_STATIC_IDENTITY存储设备的公钥和私钥。NoiseSetStaticIdentityPrivateKey设置私钥并生成公钥同时更新 Cookie 检查器的设备密钥。NoiseStaticIdentityClear安全清零私钥。3.2 握手状态机NOISE_HANDSHAKE状态包括HANDSHAKE_ZEROED、CREATED_INITIATION、CONSUMED_INITIATION、CREATED_RESPONSE、CONSUMED_RESPONSE。核心函数NoiseHandshakeInit初始化握手上下文绑定对等体的公钥和预共享密钥。NoiseHandshakeCreateInitiation发起方构造初始消息包含 ephemeral 公钥、加密的静态公钥、时间戳。NoiseHandshakeConsumeInitiation接收方验证并处理初始消息若成功则转入CONSUMED_INITIATION。NoiseHandshakeCreateResponse接收方构造响应消息包含 ephemeral 公钥、加密的空数据。NoiseHandshakeConsumeResponse发起方验证响应消息若成功则转入CONSUMED_RESPONSE。NoiseHandshakeBeginSession在握手完成后派生发送/接收密钥并替换到NOISE_KEYPAIRS中。3.3 密钥派生HKDFKdf函数实现 HKDF基于 BLAKE2s-256用于从 DH 输出派生出链密钥和对称密钥。MixDh、MixPrecomputedDh、MixPsk封装了 DH 运算与 KDF 的组合。MixHash更新握手哈希。3.4 密钥对管理NOISE_KEYPAIRS每个对等体维护三个密钥对CurrentKeypair当前活跃、PreviousKeypair上一个、NextKeypair待确认。NoiseKeypairGet/Put管理引用计数KREF。NoiseReceivedWithKeypair当收到第一个使用新密钥对的数据包时将NextKeypair提升为CurrentKeypair。NoiseExpireCurrentPeerKeypairs清除旧密钥触发重新握手。3.5 重放保护计数器NOISE_REPLAY_COUNTER实现 RFC 6479 位图滑动窗口用于接收端防重放攻击。CounterValidate检查接收到的 nonce计数器是否在窗口内且未被使用。4. 消息格式messages.h定义了四种消息类型MESSAGE_TYPE_HANDSHAKE_INITIATION1MESSAGE_TYPE_HANDSHAKE_RESPONSE2MESSAGE_TYPE_HANDSHAKE_COOKIE3MESSAGE_TYPE_DATA4各结构体包含头部、索引、公钥/临时公钥、加密载荷和 MACMac1/Mac2。其中数据包结构MESSAGE_DATA包含密钥索引、计数器nonce和加密后的 IP 包含 Poly1305 标签。常量定义了密钥长度、Cookie 长度、最大 MTU 等。5. 数据包发送路径send.c5.1 发送流程上层SendNetBufferLists将 IP 包放入对等体的StagedPacketQueue然后触发PacketSendStagedPackets。PacketSendStagedPackets执行从暂存队列中取出所有包。获取当前发送密钥CurrentKeypair。为每个包分配一个单调递增的 nonce计数器若计数器达到REJECT_AFTER_MESSAGES则拒绝。将这些包以 NBL 列表提交到EncryptQueue并唤醒加密工作线程。5.2 加密工作线程PacketEncryptWorker从EncryptQueue取出 NBL 列表。对每个 NBL遍历其NET_BUFFER调用EncryptPacket使用 SIMD 优化。EncryptPacket将 IP 包作为明文加上头部MESSAGE_DATA用 ChaCha20-Poly1305 加密并计算填充使总长度对齐到 16 字节倍数。加密完成后将 NBL 放入对等体的TxQueue并触发发送线程通过QueueEnqueuePerPeer和ProcessPerPeerWork。5.3 发送完成PacketCreateDataDone从TxQueue取出已加密的包。调用SocketSendNblsToPeer通过 WSK 发送 UDP 数据报。更新统计信息TxBytes、ifHCOutOctets 等。触发KeepKeyFresh检查是否需要重新握手和TimersDataSent。5.4 特殊包PacketSendKeepalive发送长度为 0 的数据包仅包含认证 tag用于维持连接。PacketSendHandshakeInitiation/Response/Cookie分别构造并发送握手和 Cookie 消息添加 MAC。6. 数据包接收路径receive.c6.1 接收入口PacketReceive由 WSK 接收回调或套接字处理调用传入一个 NBL 列表来自 UDP socket。PacketReceive首先检查消息类型通过读取前 4 字节握手消息Initiation/Response/Cookie放入HandshakeRxQueue由PacketHandshakeRxWorker处理。数据消息放入DecryptQueue由PacketDecryptWorker处理。6.2 握手接收ReceiveHandshakePacket调用CookieValidatePacket验证 Mac1 和 Mac2若需要 Cookie。若为 Initiation调用NoiseHandshakeConsumeInitiation解析并创建响应PacketSendHandshakeResponse。若为 Response调用NoiseHandshakeConsumeResponse成功后调用NoiseHandshakeBeginSession建立会话并发送保活包确认。6.3 数据接收PacketDecryptWorker从DecryptQueue取出 NBL对每个包调用DecryptPacket使用当前接收密钥。DecryptPacket调用ChaCha20Poly1305DecryptMdl解密并提取 nonce。解密成功后将包放入对等体的RxQueue并调用PacketConsumeDataDone。6.4 数据包验证PacketConsumeDataDone检查 nonce 是否通过重放检测CounterValidate。提取 IP 头验证协议IPv4/IPv6和长度。调用AllowedIpsLookupSrc检查源 IP 是否属于该对等体反向路径验证。若验证通过设置校验和卸载标志硬件已验证然后调用NdisMIndicateReceiveNetBufferLists将 IP 包交付给上层协议栈。更新统计信息并触发定时器。7. 套接字通信socket.c7.1 WSK 初始化WskInit注册 WSK 客户端查询可用传输IPv4/UDP、IPv6/UDP并设置静态事件回调WSK_EVENT_RECEIVE_FROM。WskUnload注销并释放资源。7.2 创建和绑定套接字CreateAndBindSocket为每个地址族创建 UDP 套接字设置IP_PKTINFO或IPV6_PKTINFO以获取接收源地址和发送接口。绑定到指定端口由用户配置或随机。7.3 接收回调Receive当 WSK 收到数据报时调用将数据报封装成NET_BUFFER_LIST并调用PacketReceive。7.4 发送函数SocketSendNblsToPeer发送多个 NBL 到对等体使用WskSendMessages支持控制消息包含源地址绑定信息。SocketSendBufferToPeer发送单个缓冲区用于握手消息。SocketSendBufferAsReplyToNbl发送响应到触发 NBL 的源地址用于 Cookie。7.5 端点解析SocketEndpointFromNbl从 WSK 提供的WSK_DATAGRAM_INDICATION中提取远程地址和IP_PKTINFO/IPV6_PKTINFO填充ENDPOINT结构包含源地址和路由信息。SocketSetPeerEndpoint和SocketSetPeerEndpointFromNbl用于更新对等体的端点信息。7.6 路由解析SocketResolvePeerEndpoint当端点发生变化时查询 IP 路由表GetIpForwardTable2选择最佳源接口和源地址并填充到ENDPOINT的Src4/Src6中用于发送时绑定源地址。8. 队列与多核调度queueing.c / queueing.h8.1 多核工作队列MULTICORE_WORKQUEUE为每个 CPU 创建一个工作线程动态响应 CPU 热插拔。工作线程通过KeWaitForMultipleObjects等待NewWork事件被唤醒后执行指定的工作函数如PacketEncryptWorker。MulticoreWorkQueueBump唤醒一个线程。8.2 PREV_QUEUE生产者-消费者队列用于对等体的TxQueue和RxQueue。支持多生产者通过Interlocked操作和单消费者通过指针操作。PrevQueueEnqueue、PrevQueueDequeue、PrevQueuePeek提供无锁队列操作。8.3 PEER_SERIAL序列化队列用于将对等体加入全局处理队列如发送队列保证同一对等体不会同时被多个线程处理避免乱序。PeerSerialEnqueueIfNotBusy和PeerSerialDequeue实现。9. 定时器管理timers.c / timers.h9.1 定时器结构每个对等体包含多个TIMER对象TimerRetransmitHandshake重传握手若未收到响应。TimerSendKeepalive发送保活包。TimerNewHandshake若长时间未收到数据触发新握手。TimerZeroKeyMaterial清除过时密钥。TimerPersistentKeepalive用户配置的持久保活间隔。9.2 回调函数ExpiredRetransmitHandshake增加尝试次数若超过最大值则放弃并清空暂存包。ExpiredSendKeepalive发送保活包。ExpiredNewHandshake发起新的握手。ExpiredZeroKeyMaterial清除所有密钥并触发清理。ExpiredSendPersistentKeepalive按用户间隔发送保活。9.3 定时器操作TimersDataSent/TimersDataReceived在数据包收发时更新保活定时器。TimersHandshakeInitiated/TimersHandshakeComplete管理握手重试。TimersAnyAuthenticatedPacketSent/Received取消相应的定时器。TimersSessionDerived设置密钥过期定时器。TimersInit/TimersStop初始化/停止对等体所有定时器。10. 总结第二篇涵盖了 WireGuardNT 驱动的核心数据面从加密原语到协议握手再到数据包的发送和接收。主要特点包括高度优化的加密实现充分利用现代 CPU 指令集。基于 Noise 协议的安全握手支持 PSK 和 Cookie 防御 DoS。高效的数据包流水线多核并行加密/解密。精细的定时器管理保证连接稳定性和自动重连。与 Windows 套接字WSK的无缝集成支持 IPv4/IPv6。后续文档将重点介绍辅助模块AllowedIPs 查找、Cookie 机制、速率限制、RCU 同步以及调试和测试支持。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价