windows 驱动实例分析系列: wintun驱动分析-example篇(下)

📅 2026/8/13 10:34:21
windows 驱动实例分析系列: wintun驱动分析-example篇(下)
(接上文)二、核心代码逐段分析2.5 收发线程ReceivePackets SendPackets2.5.1 接收线程staticDWORD WINAPIReceivePackets(DWORD_PTR SessionPtr){WINTUN_SESSION_HANDLE Session(WINTUN_SESSION_HANDLE)SessionPtr;HANDLE WaitHandles[]{WintunGetReadWaitEvent(Session),QuitEvent};while(!HaveQuit){DWORD PacketSize;BYTE*PacketWintunReceivePacket(Session,PacketSize);if(Packet){PrintPacket(Packet,PacketSize);WintunReleaseReceivePacket(Session,Packet);}else{DWORD LastErrorGetLastError();switch(LastError){caseERROR_NO_MORE_ITEMS:if(WaitForMultipleObjects(_countof(WaitHandles),WaitHandles,FALSE,INFINITE)WAIT_OBJECT_0)continue;returnERROR_SUCCESS;default:LogError(LPacket read failed,LastError);returnLastError;}}}returnERROR_SUCCESS;}关键点使用WintunGetReadWaitEvent获取一个事件句柄当环形缓冲区中有新数据时该事件会被置位。若WintunReceivePacket返回ERROR_NO_MORE_ITEMS则阻塞等待事件或退出事件避免忙等浪费 CPU。收到包后调用PrintPacket打印信息然后立即WintunReleaseReceivePacket释放缓冲区以便驱动复用。2.5.2 发送线程staticDWORD WINAPISendPackets(DWORD_PTR SessionPtr){WINTUN_SESSION_HANDLE Session(WINTUN_SESSION_HANDLE)SessionPtr;while(!HaveQuit){BYTE*PacketWintunAllocateSendPacket(Session,28);if(Packet){MakeICMP(Packet);WintunSendPacket(Session,Packet);}elseif(GetLastError()!ERROR_BUFFER_OVERFLOW)returnLogLastError(LPacket write failed);switch(WaitForSingleObject(QuitEvent,1000/* 1 second */)){caseWAIT_ABANDONED:caseWAIT_OBJECT_0:returnERROR_SUCCESS;}}returnERROR_SUCCESS;}每秒构造并发送一个 28 字节的 ICMP Echo Request 包。如果发送缓冲区已满ERROR_BUFFER_OVERFLOW则静默丢弃该包这是 Wintun 推荐的做法避免阻塞。通过WaitForSingleObject(QuitEvent, 1000)实现每秒一次发送同时能及时响应退出信号。2.6 主流程mainint__cdeclmain(void){// 1. 加载 Wintun DLLHMODULE WintunInitializeWintun();if(!Wintun)returnLogError(LFailed to initialize Wintun,GetLastError());WintunSetLogger(ConsoleLogger);Log(WINTUN_LOG_INFO,LWintun library loaded);// 2. 创建退出事件和信号处理器QuitEventCreateEventW(NULL,TRUE,FALSE,NULL);SetConsoleCtrlHandler(CtrlHandler,TRUE);// 3. 创建适配器指定固定 GUIDGUID ExampleGuid{0xdeadbabe,0xcafe,0xbeef,{0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef}};WINTUN_ADAPTER_HANDLE AdapterWintunCreateAdapter(LDemo,LExample,ExampleGuid);if(!Adapter){/* 错误处理 */}// 4. 获取驱动版本并打印DWORD VersionWintunGetRunningDriverVersion();Log(WINTUN_LOG_INFO,LWintun v%u.%u loaded,(Version16)0xff,(Version0)0xff);// 5. 配置 IP 地址10.6.7.7/24MIB_UNICASTIPADDRESS_ROW AddressRow;InitializeUnicastIpAddressEntry(AddressRow);WintunGetAdapterLUID(Adapter,AddressRow.InterfaceLuid);AddressRow.Address.Ipv4.sin_familyAF_INET;AddressRow.Address.Ipv4.sin_addr.S_un.S_addrhtonl((1024)|(616)|(78)|(70));AddressRow.OnLinkPrefixLength24;AddressRow.DadStateIpDadStatePreferred;LastErrorCreateUnicastIpAddressEntry(AddressRow);if(LastError!ERROR_SUCCESSLastError!ERROR_OBJECT_ALREADY_EXISTS){/* 错误处理 */}// 6. 启动 Wintun 会话容量 4MiBWINTUN_SESSION_HANDLE SessionWintunStartSession(Adapter,0x400000);if(!Session){/* 错误处理 */}// 7. 创建两个工作线程收、发HANDLE Workers[2];Workers[0]CreateThread(NULL,0,ReceivePackets,(LPVOID)Session,0,NULL);Workers[1]CreateThread(NULL,0,SendPackets,(LPVOID)Session,0,NULL);// 等待线程结束直到收到退出信号WaitForMultipleObjectsEx(2,Workers,TRUE,INFINITE,TRUE);// 8. 清理资源逆序// 结束会话、关闭适配器、释放 DLL 等}流程总结动态加载wintun.dll并获取所有函数指针。设置日志回调便于观察内部事件。创建名为Demo、类型为Example的 Wintun 适配器指定 GUID固定便于测试。为适配器分配 IP 地址10.6.7.7/24使用CreateUnicastIpAddressEntry。启动会话容量为0x4000004,194,304 字节即 4 MiB这是合理的中间值。创建接收线程和发送线程前者阻塞等待数据后者每秒发送一个 ICMP 请求。主线程等待线程结束通过 CtrlC 触发然后按顺序清理。三、项目配置文件分析3.1 example.vcxprojPropertyGroupLabelConfigurationConfigurationTypeApplication/ConfigurationTypePlatformToolsetWindowsApplicationForDrivers10.0/PlatformToolsetForcedTargetVersionWindows10/ForcedTargetVersion/PropertyGroupItemDefinitionGroupClCompileAdditionalIncludeDirectories..\api/AdditionalIncludeDirectories/ClCompileLinkAdditionalDependenciesiphlpapi.lib;kernel32.lib;ntdll.lib;ws2_32.lib;%(AdditionalDependencies)/AdditionalDependencies/Link/ItemDefinitionGroupItemGroupProjectReferenceInclude..\api\api.vcxprojProject{897f02e3-3eaa-40af-a6dc-17eb2376edaf}/Project/ProjectReference/ItemGroup关键点使用WindowsApplicationForDrivers10.0工具集使得可以链接ntdll.lib并调用NtQuerySystemTime等原生 API。添加..\api到包含目录以引用wintun.h头文件。链接iphlpapi.libCreateUnicastIpAddressEntry、InitializeUnicastIpAddressEntry、ntdll.libNtQuerySystemTime、ws2_32.lib字节序转换函数。项目引用api.vcxproj确保在构建示例前先编译wintun.dll虽然示例实际运行时动态加载但项目依赖用于生成正确的输出目录。3.2 example.vcxproj.filters只是一个标准的源文件筛选器将example.c归入“Source Files”组无特殊之处。四、编译与运行编译在 Visual Studio 中打开wintun.sln编译example项目它会自动依赖api项目生成wintun.dll。运行前准备确保wintun.dll与example.exe在同一目录或系统目录。如果驱动未安装example.exe会通过WintunCreateAdapter自动安装需要管理员权限。运行以管理员身份打开命令提示符执行example.exe。控制台会显示日志每秒发送一个 ICMP 请求并在收到回复时打印。停止按 CtrlC 触发清理程序退出。预期输出示例2026-08-10 10:00:00.1234 [] Wintun library loaded 2026-08-10 10:00:00.1256 [] Wintun v0.14 loaded 2026-08-10 10:00:01.1278 [] Sending IPv4 ICMP echo request to 10.6.7.8 from 10.6.7.7 2026-08-10 10:00:01.1301 [] Received IPv4 ICMP echo reply from 10.6.7.7 to 10.6.7.8 ...五、设计评价与教学价值5.1 优点完整生命周期从 DLL 加载、适配器创建、IP 配置、数据收发到优雅退出覆盖了所有关键 API。健壮的错误处理每步操作都检查返回值并通过日志输出详细错误信息。多线程示范展示了如何并发处理收发包以及如何利用事件对象协调线程。自包含测试通过构造 ICMP 包并回环接收无需外部设备即可验证驱动功能。清晰的代码风格函数职责单一注释虽少但命名自解释。5.2 教学意义可作为新手入门 Wintun 的第一份代码快速理解 API 用法。展示了 Windows 网络编程中的常见模式延迟加载 DLL、错误码格式化、控制台信号处理、IP 地址配置。可扩展为实际 VPN 客户端的骨架只需修改MakeICMP为隧道数据封装并添加路由表操作。5.3 潜在改进点发送线程固定每秒一个包实际应用可能需要动态调整速率。未处理 IPv6 情况虽然接收端支持但发送端只构造 IPv4。未展示如何设置默认网关或路由表这对 VPN 是必需的。六、总结example文件夹虽然仅包含一个源文件但它浓缩了 Wintun 使用的所有精华。它不仅是项目的“门面示例”更是 WireGuard 团队为开发者精心准备的可工作的最小原型。通过这个示例开发者可以快速搭建起自己的隧道应用而无需从头研究 Windows 网络栈的复杂细节。评价这是一个教科书级别的示例程序兼具实用性和教学性值得所有 Windows 网络开发者学习和借鉴。