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

(接上文)

二、核心代码逐段分析

2.5 收发线程(ReceivePackets & SendPackets)

2.5.1 接收线程
c 复制代码
static DWORD WINAPI ReceivePackets(DWORD_PTR SessionPtr)
{
    WINTUN_SESSION_HANDLE Session = (WINTUN_SESSION_HANDLE)SessionPtr;
    HANDLE WaitHandles[] = { WintunGetReadWaitEvent(Session), QuitEvent };
    while (!HaveQuit) {
        DWORD PacketSize;
        BYTE *Packet = WintunReceivePacket(Session, &PacketSize);
        if (Packet) {
            PrintPacket(Packet, PacketSize);
            WintunReleaseReceivePacket(Session, Packet);
        } else {
            DWORD LastError = GetLastError();
            switch (LastError) {
            case ERROR_NO_MORE_ITEMS:
                if (WaitForMultipleObjects(_countof(WaitHandles), WaitHandles, FALSE, INFINITE) == WAIT_OBJECT_0)
                    continue;
                return ERROR_SUCCESS;
            default:
                LogError(L"Packet read failed", LastError);
                return LastError;
            }
        }
    }
    return ERROR_SUCCESS;
}

关键点

  • 使用 WintunGetReadWaitEvent 获取一个事件句柄,当环形缓冲区中有新数据时,该事件会被置位。
  • WintunReceivePacket 返回 ERROR_NO_MORE_ITEMS,则阻塞等待事件(或退出事件),避免忙等浪费 CPU。
  • 收到包后,调用 PrintPacket 打印信息,然后立即 WintunReleaseReceivePacket 释放缓冲区,以便驱动复用。
2.5.2 发送线程
c 复制代码
static DWORD WINAPI SendPackets(DWORD_PTR SessionPtr)
{
    WINTUN_SESSION_HANDLE Session = (WINTUN_SESSION_HANDLE)SessionPtr;
    while (!HaveQuit) {
        BYTE *Packet = WintunAllocateSendPacket(Session, 28);
        if (Packet) {
            MakeICMP(Packet);
            WintunSendPacket(Session, Packet);
        } else if (GetLastError() != ERROR_BUFFER_OVERFLOW)
            return LogLastError(L"Packet write failed");
        switch (WaitForSingleObject(QuitEvent, 1000 /* 1 second */)) {
        case WAIT_ABANDONED:
        case WAIT_OBJECT_0:
            return ERROR_SUCCESS;
        }
    }
    return ERROR_SUCCESS;
}
  • 每秒构造并发送一个 28 字节的 ICMP Echo Request 包。
  • 如果发送缓冲区已满(ERROR_BUFFER_OVERFLOW),则静默丢弃该包(这是 Wintun 推荐的做法,避免阻塞)。
  • 通过 WaitForSingleObject(QuitEvent, 1000) 实现每秒一次发送,同时能及时响应退出信号。

2.6 主流程(main)

c 复制代码
int __cdecl main(void)
{
    // 1. 加载 Wintun DLL
    HMODULE Wintun = InitializeWintun();
    if (!Wintun) return LogError(L"Failed to initialize Wintun", GetLastError());
    WintunSetLogger(ConsoleLogger);
    Log(WINTUN_LOG_INFO, L"Wintun library loaded");

    // 2. 创建退出事件和信号处理器
    QuitEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
    SetConsoleCtrlHandler(CtrlHandler, TRUE);

    // 3. 创建适配器(指定固定 GUID)
    GUID ExampleGuid = { 0xdeadbabe, 0xcafe, 0xbeef, { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef } };
    WINTUN_ADAPTER_HANDLE Adapter = WintunCreateAdapter(L"Demo", L"Example", &ExampleGuid);
    if (!Adapter) { /* 错误处理 */ }

    // 4. 获取驱动版本并打印
    DWORD Version = WintunGetRunningDriverVersion();
    Log(WINTUN_LOG_INFO, L"Wintun v%u.%u loaded", (Version >> 16) & 0xff, (Version >> 0) & 0xff);

    // 5. 配置 IP 地址(10.6.7.7/24)
    MIB_UNICASTIPADDRESS_ROW AddressRow;
    InitializeUnicastIpAddressEntry(&AddressRow);
    WintunGetAdapterLUID(Adapter, &AddressRow.InterfaceLuid);
    AddressRow.Address.Ipv4.sin_family = AF_INET;
    AddressRow.Address.Ipv4.sin_addr.S_un.S_addr = htonl((10 << 24) | (6 << 16) | (7 << 8) | (7 << 0));
    AddressRow.OnLinkPrefixLength = 24;
    AddressRow.DadState = IpDadStatePreferred;
    LastError = CreateUnicastIpAddressEntry(&AddressRow);
    if (LastError != ERROR_SUCCESS && LastError != ERROR_OBJECT_ALREADY_EXISTS) { /* 错误处理 */ }

    // 6. 启动 Wintun 会话(容量 4MiB)
    WINTUN_SESSION_HANDLE Session = WintunStartSession(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 等
}

流程总结

  1. 动态加载 wintun.dll 并获取所有函数指针。
  2. 设置日志回调,便于观察内部事件。
  3. 创建名为 Demo、类型为 Example 的 Wintun 适配器,指定 GUID(固定,便于测试)。
  4. 为适配器分配 IP 地址 10.6.7.7/24(使用 CreateUnicastIpAddressEntry)。
  5. 启动会话,容量为 0x400000(4,194,304 字节,即 4 MiB),这是合理的中间值。
  6. 创建接收线程和发送线程,前者阻塞等待数据,后者每秒发送一个 ICMP 请求。
  7. 主线程等待线程结束(通过 Ctrl+C 触发),然后按顺序清理。

三、项目配置文件分析

3.1 example.vcxproj

xml 复制代码
<PropertyGroup Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <PlatformToolset>WindowsApplicationForDrivers10.0</PlatformToolset>
    <ForcedTargetVersion>Windows10</ForcedTargetVersion>
</PropertyGroup>
<ItemDefinitionGroup>
    <ClCompile>
        <AdditionalIncludeDirectories>..\api</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
        <AdditionalDependencies>iphlpapi.lib;kernel32.lib;ntdll.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
</ItemDefinitionGroup>
<ItemGroup>
    <ProjectReference Include="..\api\api.vcxproj">
        <Project>{897f02e3-3eaa-40af-a6dc-17eb2376edaf}</Project>
    </ProjectReference>
</ItemGroup>

关键点

  • 使用 WindowsApplicationForDrivers10.0 工具集,使得可以链接 ntdll.lib 并调用 NtQuerySystemTime 等原生 API。
  • 添加 ..\api 到包含目录,以引用 wintun.h 头文件。
  • 链接 iphlpapi.libCreateUnicastIpAddressEntryInitializeUnicastIpAddressEntry)、ntdll.libNtQuerySystemTime)、ws2_32.lib(字节序转换函数)。
  • 项目引用 api.vcxproj,确保在构建示例前先编译 wintun.dll(虽然示例实际运行时动态加载,但项目依赖用于生成正确的输出目录)。

3.2 example.vcxproj.filters

只是一个标准的源文件筛选器,将 example.c 归入"Source Files"组,无特殊之处。


四、编译与运行

  1. 编译 :在 Visual Studio 中打开 wintun.sln,编译 example 项目(它会自动依赖 api 项目生成 wintun.dll)。
  2. 运行前准备 :确保 wintun.dllexample.exe 在同一目录(或系统目录)。如果驱动未安装,example.exe 会通过 WintunCreateAdapter 自动安装(需要管理员权限)。
  3. 运行 :以管理员身份 打开命令提示符,执行 example.exe。控制台会显示日志,每秒发送一个 ICMP 请求,并在收到回复时打印。
  4. 停止:按 Ctrl+C 触发清理,程序退出。

预期输出示例

复制代码
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 网络开发者学习和借鉴。

相关推荐
怦怦蓝4 小时前
Windows 安装 Docker Desktop 踩坑完整实战指南(WSL2 故障排错实录)
windows·docker·容器
海盗12346 小时前
微软技术日报·2026-08-12——Windows 11多通道累积更新推送十余项功能改进;.NET WebSocket DoS紧急修复
windows·microsoft·.net
for_ever_love__21 小时前
python基础语法学习: 数据容器
windows·python·学习
huainingning1 天前
微软windows系统官网IPV6临时地址介绍
windows·microsoft·智能路由器
.Peter1 天前
.NET/WPF 程序在部分 Windows 电脑无法保存用户环境变量:使用 DPAPI 实现安全兼容
windows·信息安全·c#·.net·wpf·环境变量·dpapi
码字的特恩1 天前
微软确认暂不为 Windows 11 加入透明效果自定义功能,建议用户使用第三方工具
人工智能·windows·算法·microsoft·计算机·大模型·编程
神奇小梵1 天前
安全和开发的需要了解的知识————下载内容的安全,和下载内容如何运行
android·windows·安全·个人开发
zhangfeng11331 天前
Windows AMD显卡跑PyTorch
人工智能·pytorch·windows
楷哥爱开发1 天前
如何在 Windows、macOS 和 Linux 上安装 scikit-learn (sklearn)
linux·windows·macos