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

发布时间:2026/10/3 14:31:00
windows 驱动实例分析系列: wintun驱动分析-example篇(上) Wintun Example 示例程序深度解析这部分作为演示如何使用wintun驱动进行二次开发的例子分为两部分。一、模块概述example文件夹是 Wintun 项目提供的一个完整的、可直接运行的示例程序用于演示如何使用wintun.dll的 API 来创建虚拟网络适配器、配置 IP 地址、收发数据包ICMP 回显请求/响应。它充当了一个最小化 VPN 客户端原型展示了从初始化到清理的完整生命周期。这个示例不仅是一个教学工具也是 Wintun 开发者进行集成测试的基准程序。其代码风格严谨、错误处理周全完全可以作为实际项目开发的参考模板。二、核心代码逐段分析2.1 函数指针与动态加载InitializeWintunstaticWINTUN_CREATE_ADAPTER_FUNC*WintunCreateAdapter;// ... 其余 13 个函数指针staticHMODULEInitializeWintun(void){HMODULE WintunLoadLibraryExW(Lwintun.dll,NULL,LOAD_LIBRARY_SEARCH_APPLICATION_DIR|LOAD_LIBRARY_SEARCH_SYSTEM32);if(!Wintun)returnNULL;#defineX(Name)((*(FARPROC*)NameGetProcAddress(Wintun,#Name))NULL)if(X(WintunCreateAdapter)||X(...)||/* ... */)#undefX{/* 错误处理并释放 */}returnWintun;}设计要点使用LoadLibraryEx并指定LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32确保优先从应用目录和系统目录加载wintun.dll防止 DLL 劫持。通过宏X批量获取所有导出函数的地址若任一失败则回滚并报错。返回的HMODULE在程序结束时释放确保资源正确清理。这种做法体现了延迟绑定的思想不静态链接wintun.lib而是运行时动态加载使得示例程序可以在未安装 Wintun 的系统上优雅地报错退出也方便替换不同版本的 DLL 进行测试。2.2 日志系统ConsoleLogger Log 辅助staticvoidCALLBACKConsoleLogger(WINTUN_LOGGER_LEVEL Level,DWORD64 Timestamp,constWCHAR*LogLine){SYSTEMTIME SystemTime;FileTimeToSystemTime((FILETIME*)Timestamp,SystemTime);WCHAR LevelMarker(LevelWINTUN_LOG_INFO)?L:(LevelWINTUN_LOG_WARN)?L-:L!;fwprintf(stderr,L%04u-%02u-%02u %02u:%02u:%02u.%04u [%c] %s\n,...);}亮点实现了 Wintun 要求的WINTUN_LOGGER_CALLBACK回调原型通过WintunSetLogger注册使 Wintun 内部日志也能输出到控制台。时间戳100 ns 间隔自 1601-01-01被正确转换为本地时间便于阅读。用、-、!分别代表 Info、Warn、Error 级别直观简洁。此外程序还提供了LogError、LogLastError、Log三个辅助函数LogError利用FormatMessage将 Win32 错误码转换为可读的字符串并自动添加前缀。LogLastError封装GetLastError()LogError简化调用。Log变参函数格式化输出到日志回调。这组日志工具使得程序在任何错误发生时都能输出详尽的诊断信息极大提升了调试效率。2.3 控制台信号处理优雅退出staticHANDLE QuitEvent;staticvolatileBOOL HaveQuit;staticBOOL WINAPICtrlHandler(DWORD CtrlType){switch(CtrlType){caseCTRL_C_EVENT:caseCTRL_BREAK_EVENT:caseCTRL_CLOSE_EVENT:caseCTRL_LOGOFF_EVENT:caseCTRL_SHUTDOWN_EVENT:Log(WINTUN_LOG_INFO,LCleaning up and shutting down...);HaveQuitTRUE;SetEvent(QuitEvent);returnTRUE;}returnFALSE;}机制HaveQuit是volatile全局标志供工作线程轮询。QuitEvent是一个手动重置事件用于唤醒阻塞在WaitForSingleObject上的线程。当用户按下 CtrlC 或关闭控制台时CtrlHandler被调用设置标志并触发事件所有线程收到信号后自行退出。这保证了示例程序在退出前能正确调用WintunEndSession和WintunCloseAdapter避免驱动资源泄漏。2.4 数据包处理逻辑PrintPacket MakeICMP2.4.1 收包解析PrintPacketstaticvoidPrintPacket(constBYTE*Packet,DWORD PacketSize){if(PacketSize20){/* 错误 */return;}BYTE IpVersionPacket[0]4,Proto;WCHAR Src[46],Dst[46];if(IpVersion4){RtlIpv4AddressToStringW((structin_addr*)Packet[12],Src);RtlIpv4AddressToStringW((structin_addr*)Packet[16],Dst);ProtoPacket[9];Packet20;PacketSize-20;}elseif(IpVersion6){// 检查最小长度 40 字节RtlIpv6AddressToStringW((structin6_addr*)Packet[8],Src);RtlIpv6AddressToStringW((structin6_addr*)Packet[24],Dst);ProtoPacket[6];Packet40;PacketSize-40;}else{Log(WINTUN_LOG_INFO,LReceived packet that was not IP);return;}// 如果协议是 ICMP (1) 且是回显应答 (type0)特殊标记if(Proto1PacketSize8Packet[0]0)Log(WINTUN_LOG_INFO,LReceived IPv%d ICMP echo reply from %s to %s,IpVersion,Src,Dst);elseLog(WINTUN_LOG_INFO,LReceived IPv%d proto 0x%x packet from %s to %s,IpVersion,Proto,Src,Dst);}解析要点通过 IPv4 首部第一个字节的高 4 位判断版本区分 IPv4 和 IPv6。使用RtlIpv4AddressToStringW/RtlIpv6AddressToStringW将二进制地址转换为字符串需链接ip2string.h和ntdll.lib。对于 ICMP 回显应答Echo Replytype0专门输出“ICMP echo reply”提示便于观察 ping 响应。2.4.2 发包构造MakeICMPstaticvoidMakeICMP(BYTE Packet[28]){memset(Packet,0,28);Packet[0]0x45;// IPv4, 首部长度 5*(USHORT*)Packet[2]htons(28);// 总长度 28Packet[8]255;// TTLPacket[9]1;// 协议 ICMP*(ULONG*)Packet[12]htonl((1024)|(616)|(78)|(80));// 源 10.6.7.8*(ULONG*)Packet[16]htonl((1024)|(616)|(78)|(70));// 目的 10.6.7.7*(USHORT*)Packet[10]IPChecksum(Packet,20);// IPv4 首部校验和Packet[20]8;// ICMP 类型: Echo Request*(USHORT*)Packet[22]IPChecksum(Packet[20],8);// ICMP 校验和Log(WINTUN_LOG_INFO,LSending IPv4 ICMP echo request to 10.6.7.8 from 10.6.7.7);}注意构造的是ICMP Echo Requestping 请求源地址为10.6.7.8目的地址为10.6.7.7。但示例程序在后续配置中将适配器的 IP 设为了10.6.7.7见后文这意味着它将 ping自身——实际上数据包会由操作系统路由回本机然后被ReceivePackets捕获并打印为“ICMP echo reply”不对因为源地址是 10.6.7.8而适配器是 10.6.7.7所以实际上是从 10.6.7.8 发往 10.6.7.7但源地址 10.6.7.8 并未分配给任何接口因此这个包会进入网络栈并被路由由于目标 10.6.7.7 是本机系统会响应 ICMP Echo Reply但源地址仍为 10.6.7.7目标为 10.6.7.8。所以最终接收到的包应该是 Echo ReplyPrintPacket 会识别出 type0输出“ICMP echo reply”。这个设计巧妙模拟了 ping 环回测试。IPv4 校验和计算函数IPChecksum实现了标准的一进制补码求和支持任意长度。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询