WireGuardNT 驱动代码解析 - 第一篇:设备管理与IOCTL接口
1. 概述
WireGuardNT 驱动(driver/ 目录)是 WireGuard 在 Windows NT 内核中的核心实现,负责处理 VPN 隧道的数据包加解密、路由、对等体管理以及网络接口操作。本系列文档将驱动代码分为四个部分进行解析,本文为第一部分,涵盖设备初始化、适配器管理、IOCTL 控制接口以及与网络状态指示器(NSI)的交互。
主要涉及以下源文件:
main.c-- 驱动入口点(DriverEntry)和卸载例程。device.c/device.h-- 定义 WG_DEVICE 结构,实现 NDIS 微型端口驱动回调(初始化、暂停、重启、发送、接收、OID 请求等)。ioctl.c/ioctl.h-- 实现用户态与驱动通信的 IOCTL 接口(获取/设置配置、适配器状态、读取日志)。nsi.c/nsi.h-- 与 Windows 网络状态指示器(NSI)集成,监控 MTU 变化并拦截 NSI 设置请求。wireguard.inf-- 驱动安装信息文件。wireguard.rc/wireguard.ico-- 资源文件。
2. 驱动入口与初始化(main.c)
DriverEntry 是驱动的初始化入口,其流程如下:
- 调用
ExInitializeDriverRuntime(DrvRtPoolNxOptIn)启用非执行池保护。 - 调用
CryptoDriverEntry()检测 CPU 特性(SSSE3、AVX、AVX2、AVX512 等),为后续加密算法选择最佳实现。 - 调用
NoiseDriverEntry()预计算 Noise 握手协议所需的初始哈希和链密钥(基于固定字符串)。 - 调用
MemDriverEntry()初始化内存池(NET_BUFFER 和 NET_BUFFER_LIST 缓存)。 - 调用
RcuDriverEntry()初始化 RCU 机制(每 CPU 的 DPC 和清理线程)。 - 调用
AllowedIpsDriverEntry()初始化 AllowedIPs 查找树的节点缓存。 - 调用
RatelimiterDriverEntry()初始化速率限制器(GC 线程和哈希表)。 - 调用
PeerDriverEntry()初始化对等体对象的后备列表(lookaside list)。 - 调用
DeviceDriverEntry()注册 NDIS 微型端口驱动(NdisMRegisterMiniportDriver)。 - 调用
IoctlDriverEntry()和NsiDriverEntry()分别挂接 IOCTL 派遣例程和 NSI 过滤。
若调试版本(DBG)启用了自测试,还会执行 CryptoSelftest、AllowedIpsSelftest、PacketCounterSelftest 和 RatelimiterSelftest。
卸载例程 Unload 依次调用 DeviceUnload、WskUnload、PeerUnload、RatelimiterUnload、AllowedIpsUnload、RcuUnload、MemUnload 释放资源。
3. NDIS 微型端口驱动(device.c)
device.c 实现了 NDIS 微型端口驱动所需的回调函数,并管理 WG_DEVICE 结构(每个适配器一个实例)。
3.1 核心数据结构 WG_DEVICE
定义在 device.h 中,包含:
MiniportAdapterHandle-- NDIS 适配器句柄。Statistics-- 接口统计信息(收发字节、包数等)。ItemsInFlight-- 引用计数,用于控制暂停/重启时的资源释放。- 多个环形队列(
EncryptQueue、DecryptQueue、HandshakeRxQueue)用于异步处理。 - 多核工作队列(
EncryptThreads、DecryptThreads、HandshakeTxThreads、HandshakeRxThreads)用于并行加解密和握手。 - 套接字指针
Sock4和Sock6(RCU 保护)。 - 静态身份标识
StaticIdentity(公钥/私钥)。 - Cookie 检查器
CookieChecker。 - 对等体哈希表
PeerHashtable和索引哈希表IndexHashtable。 - AllowedIPs 表
PeerAllowedIps。 - 设备锁
DeviceUpdateLock和套接字更新锁SocketUpdateLock。 - 对等体列表
PeerList和计数NumPeers。 - 接口索引、LUID、属主进程、监听端口、UP/DOWN 状态等。
3.2 NDIS 回调函数
-
InitializeEx:创建WG_DEVICE实例,初始化所有队列、锁、哈希表、工作线程,并调用RegisterAdapter注册适配器属性。若成功,调用NsiActivate激活 NSI 监控。 -
HaltEx:反向清理,关闭套接字、移除所有对等体、销毁工作线程、释放内存。 -
Pause:在适配器暂停时调用,等待所有飞行中的包完成,调用DeviceStop停止设备(清空密钥、清除握手状态)。 -
Restart:恢复适配器,重新初始化ItemsInFlight,并调用DeviceStart启动对等体的待发送包和保活定时器。 -
SendNetBufferLists:处理上层协议栈发来的数据包。对每个 NBL,检查协议(IPv4/IPv6),提取 IP 头,通过AllowedIpsLookupDst查找目标对等体,然后将包加入对等体的暂存队列(StagedPacketQueue),并触发PacketSendStagedPackets发送。 -
OidRequest:处理 OID 查询和设置请求,例如返回厂商 ID、速度、统计信息、链路状态等。对于OID_GEN_CURRENT_PACKET_FILTER等设置,仅验证长度并返回成功;对于OID_PNP_SET_POWER,若进入 D1 以上状态则调用RcuBarrier。 -
ReturnNetBufferLists:释放接收到的 NBL(由PacketReceive处理后调用)。
3.3 适配器状态管理
DeviceStart 和 DeviceStop 分别启动/停止设备,其中 DeviceStart 遍历所有对等体,发送暂存包和保活包;DeviceStop 清除所有对等体的密钥和握手状态,并清空握手队列。
DeviceIndicateConnectionStatus 用于向 NDIS 报告链路连接状态(UP/DOWN),并更新链路速度。
4. IOCTL 接口(ioctl.c / ioctl.h)
用户态 DLL(wireguard.dll)通过 IOCTL 与驱动通信。驱动在 IoctlDriverEntry 中替换 IRP_MJ_DEVICE_CONTROL 的派遣例程,并设置安全描述符(允许 SYSTEM 和 Administrators 完全访问)。
定义的控制代码(ioctl.h):
WG_IOCTL_GET(METHOD_OUT_DIRECT):获取当前适配器配置(接口信息、对等体列表、AllowedIPs)。返回WG_IOCTL_INTERFACE结构及其后跟随的WG_IOCTL_PEER和WG_IOCTL_ALLOWED_IP结构。若缓冲区不足,返回STATUS_BUFFER_OVERFLOW并给出所需大小。WG_IOCTL_SET(METHOD_IN_DIRECT):设置配置。输入为一个WG_IOCTL_INTERFACE后接若干WG_IOCTL_PEER和WG_IOCTL_ALLOWED_IP。驱动解析并应用配置(设置监听端口、私钥、添加/删除对等体、更新 AllowedIPs)。WG_IOCTL_SET_ADAPTER_STATE(METHOD_BUFFERED):设置适配器状态(UP/DOWN)或查询当前状态。WG_IOCTL_READ_LOG_LINE(METHOD_BUFFERED):读取下一条日志条目(环形缓冲区)。
关键辅助函数:
HasAccess:使用安全描述符检查调用者权限。WgDeviceFromFdo:从设备对象提取WG_DEVICE指针(利用NdisWdfGetAdapterContextFromAdapterHandle)。
Get 操作 :遍历对等体和 AllowedIPs,填充输出缓冲区。注意使用锁保护(DeviceUpdateLock 和各个子锁)。
Set 操作 :解析 WG_IOCTL_INTERFACE,调用 SetListenPort、SetPrivateKey,并循环处理每个对等体(SetPeer)。SetPeer 中会查找或创建对等体,更新预共享密钥、端点、持久保活间隔,并调用 AllowedIpsInsertV4/V6 或 Remove 更新路由表。
SetAdapterState :根据命令调用 Up 或 Down,内部会创建/销毁 UDP 套接字、更新链路状态、启动/停止定时器。
ReadLogLine :从 LOG_RING 中读取一条日志(非阻塞等待,可被设备移除事件中断)。
5. NSI 集成(nsi.c / nsi.h)
为了监控和调节接口 MTU,驱动挂钩了 NSI(网络状态指示器)的 IOCTL_NSI_SET_ALL_PARAMETERS 调用。NsiDriverEntry 替换了 IRP_MJ_PNP 和 IRP_MJ_DEVICE_CONTROL 的派遣例程,并在首次有设备激活时附加到 \Device\Nsi 设备对象。
NsiActivate:将设备加入全局DeviceList,若列表原本为空,则调用Attach挂载过滤设备。NsiDeactivate:从列表中移除,若列表变空则调用Detach卸载过滤。
过滤派遣例程 FilterDispatch 拦截 IOCTL_NSI_SET_ALL_PARAMETERS,提取出 MIB_IPINTERFACE_ROW(包括接口 LUID 和 MTU),然后调用原始的 NSI 请求,并在成功后回调 IpInterfaceChangeNotification,更新设备的 Mtu4 或 Mtu6(根据地址族)。该回调也通过 NotifyRouteChange2 注册,以响应路由变化。
通过这种方式,驱动能感知上层设置的 MTU,并调整数据包填充大小,确保不超过 MTU。
6. 自测试与调试
在调试版本中,main.c 中会执行一系列自测试(加密、AllowedIPs、计数器、速率限制器),这些测试定义在 selftest/ 目录下的文件中(如 chacha20poly1305.c、allowedips.c、counter.c、ratelimiter.c)。测试失败将导致驱动加载失败(返回 STATUS_INTERNAL_ERROR)。
7. 总结
第一部分的代码构成了驱动的基础骨架,负责:
- 驱动的加载、初始化、卸载。
- NDIS 微型端口驱动的注册和回调实现。
- 用户态 IOCTL 接口,实现配置的获取、设置及适配器状态控制。
- 与 NSI 集成,自适应 MTU 变化。
后续文档将深入探讨加密与 Noise 协议、数据包收发路径以及辅助模块。