ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

windows 驱动实例分析系列: wintun驱动分析-api篇(二)

2026/8/15 22:36:18 拓冰建站 浏览量
windows 驱动实例分析系列: wintun驱动分析-api篇(二)

Wintun API 模块深度解析(文档二):适配器生命周期与驱动管理

一、概述

本文档深入分析api模块中的适配器管理和驱动安装逻辑,主要涉及文件:

  • adapter.c/adapter.h
  • adapter_win7.h
  • driver.c/driver.h
  • registry.c/registry.h(辅助读取注册表)
  • resource.c/resource.h(提取嵌入式文件)

适配器管理是 Wintun 最核心的功能之一,涵盖了从创建、打开、关闭、删除到孤儿设备清理的全过程。驱动安装则负责在首次使用或版本更新时,将 Wintun 内核驱动(wintun.sys)和配套 INF/CAT 文件部署到系统。


二、驱动安装与版本管理(driver.c

2.1 驱动版本检测

  • WintunGetRunningDriverVersion:通过NtQuerySystemInformation枚举已加载的内核模块,查找名为wintun.sys的驱动,然后读取其文件版本(通过GetFileVersionInfo)。若未加载,返回 0 并设置ERROR_FILE_NOT_FOUND
  • 内部辅助函数MaybeGetRunningDriverVersion(ReturnOneIfRunningInsteadOfVersion)支持仅检测是否存在(返回 1),用于等待驱动卸载。

2.2 驱动安装流程(DriverInstall

此函数在WintunCreateAdapter中被调用(首次创建适配器时)。流程如下:

  1. 获取互斥锁:通过NamespaceTakeDriverInstallationMutex()确保同一时刻只有一个进程执行安装,避免竞争。
  2. 创建设备信息:使用SetupDiCreateDeviceInfoListExW创建网络设备类的一个临时设备信息元素,设置硬件 ID 为Wintun
  3. 枚举已安装的兼容驱动:通过SetupDiBuildDriverInfoList获取系统中所有匹配Wintun硬件 ID 的驱动。
  4. 版本比较:遍历每个驱动信息,与 Wintun 内置驱动版本(从 INF 提取的WINTUN_INF_VERSIONWINTUN_INF_FILETIME)比较。如果现有驱动更新,则直接使用;如果内置驱动更新,则:
    • 禁用所有使用当前驱动创建的 Wintun 适配器(调用DisableAllOurAdapters),等待驱动卸载(EnsureWintunUnloaded)。
    • 调用SetupUninstallOEMInfW卸载旧驱动(SUOI_FORCEDELETE强制删除)。
    • 继续寻找下一个驱动。
  5. 若没有找到可用驱动,则执行全新安装:
    • 使用ResourceCreateTemporaryDirectory%WINDIR%\Temp下创建随机临时目录。
    • 根据本机架构(NativeMachine),从 DLL 资源中提取对应的wintun.syswintun.catwintun.inf到临时目录。
    • 调用SetupCopyOEMInfW将 INF 复制到系统驱动存储(%SystemRoot%\INF),完成安装。
    • 删除临时文件并移除目录。
  6. 保存上下文:将之前禁用的适配器列表通过输出参数DevInfoExistingAdaptersForCleanupExistingAdaptersForCleanup返回,由调用方在适配器创建完成后重新启用它们(通过DriverInstallDeferredCleanup)。

2.3 驱动卸载(WintunDeleteDriver

  • 首先调用AdapterCleanupOrphanedDevices清理所有孤儿适配器(无所有者进程)。
  • 再次枚举匹配Wintun硬件 ID 的驱动,对每个驱动调用SetupUninstallOEMInfW移除。
  • 此操作需获取驱动安装互斥锁,且仅在无适配器使用时成功。

2.4 版本比较逻辑

IsNewer函数通过比较FILETIME和 64 位版本号(高 16 位为主版本,其次为次版本,其次为构建号,低 16 位为修订)决定新旧。优先比较日期,再比较版本号。


三、适配器创建(WintunCreateAdapter

这是最复杂的函数,需要处理 Windows 7/8/10 的不同 API,并支持 WOW64 代理。

3.1 前期准备

  • 获取设备安装互斥锁(NamespaceTakeDeviceInstallationMutex)。
  • 调用DriverInstall确保驱动已安装。
  • 分配并初始化WINTUN_ADAPTER结构。

3.2 Windows 8+ 的软件设备创建路径

步骤:

  1. 生成实例 ID:若调用方提供了RequestedGUID,则直接使用;否则调用CoCreateGuid生成随机 GUID。将 GUID 转换为字符串作为设备实例 ID。
  2. 创建存根设备(Stub):在 Windows 10 上,先通过SwDeviceCreate创建一个临时的存根设备,仅设置硬件 ID 为空字符串,并在其软件注册表项中写入SuggestedInstanceId(用于影响 NLA GUID)。然后立即关闭该存根。
    • 此步骤的目的是为系统网络位置感知(NLA)提供确定的 GUID,避免每次创建新适配器都生成新的 NLA 条目。
  3. 创建主设备:再次调用SwDeviceCreate,传入实际的硬件 ID(WINTUN_HWID)和设备属性:
    • DEVPKEY_Wintun_Name:用户指定的适配器名称(如 “Demo”)。
    • DEVPKEY_Device_FriendlyName:隧道类型名称 + " Tunnel"。
    • DEVPKEY_Device_DeviceDesc:同上。
    • 设置标志SWDeviceCapabilitiesSilentInstall(静默安装)和SWDeviceCapabilitiesDriverRequired(需要驱动)。
  4. 等待设备创建完成:通过回调DeviceCreateCallback设置事件,然后等待。
  5. 等待接口可用:调用WaitForInterface,使用DevCreateObjectQuery查询设备接口(GUID_DEVINTERFACE_NET)是否已启用,超时 15 秒。这确保了驱动已正确加载并注册了设备接口。
  6. 打开设备信息:通过SetupDiOpenDeviceInfo打开该设备,然后调用PopulateAdapterData从注册表读取NetCfgInstanceIdNetLuidIndex*IfType等关键信息。
  7. 设置网络连接名称:调用NciSetAdapterName(内部使用NciSetConnectionName)将网络连接显示名设置为用户指定的名称,若冲突则自动添加序号后缀。
  8. Windows 7 特殊处理:若系统为 Windows 7,则路径不同(见下文)。

3.3 Windows 7 的专用创建路径

Windows 7 不支持SwDeviceCreate,因此 Wintun 使用传统的 Setup API 进行设备创建(参考adapter_win7.h中的CreateAdapterWin7函数):

  • 创建设备信息元素,设置硬件 ID 为Wintun
  • 构建兼容驱动列表,选中第一个匹配的驱动。
  • 依次调用DIF_REGISTERDEVICEDIF_REGISTER_COINSTALLERSDIF_INSTALLINTERFACESDIF_INSTALLDEVICE
  • 设置自定义属性DEVPKEY_Wintun_OwningProcess(包含进程 ID 和创建时间),用于孤儿设备清理。
  • 等待设备接口可用(通过轮询检查注册表值和设备状态)。
  • 返回设备实例 ID。

3.4 WOW64 代理调用

在 32 位进程运行于 64 位系统上时,直接调用 Setup API 会失败。因此,adapter.c中的AdapterRemoveInstanceAdapterEnableInstanceAdapterDisableInstance等函数会检查全局NativeMachineIMAGE_FILE_PROCESS,若不同则通过rundll32.c中的辅助函数启动 64 位代理进程(setupapihost.dll)来执行操作。

代理调用的实现细节已在之前《setupapihost 深度解析》中详细说明,此处不再重复。

3.5 适配器打开(WintunOpenAdapter

  • 枚举所有网络设备,过滤枚举器为SWD\Wintun(Win8+)或ROOT\Wintun(Win7),查找DEVPKEY_Wintun_Name与给定名称匹配的设备。
  • 获取设备实例 ID,打开设备信息,填充WINTUN_ADAPTER结构。
  • 同样等待接口可用并填充数据。

3.6 适配器关闭(WintunCloseAdapter

  • 若适配器由CreateAdapter创建(具有SwDevice或通过 Windows 7 路径),则调用AdapterRemoveInstance删除设备。
  • 否则只释放句柄,不删除(打开模式)。
  • 释放内存后,触发异步孤儿设备清理(QueueUpOrphanedDeviceCleanupRoutine)。

3.7 孤儿设备清理(AdapterCleanupOrphanedDevices

孤儿设备是指那些没有关联进程(或关联进程已退出)的 Wintun 适配器。这类设备可能因进程崩溃或未正确调用CloseAdapter而遗留。

清理逻辑(Win8+):

  • 枚举所有网络设备,检查设备状态是否有问题(CM_Get_DevNode_StatusDN_HAS_PROBLEM)。
  • 对于有问题的设备,尝试通过DEVPKEY_Wintun_Name获取名称,并调用AdapterRemoveInstance删除。

Windows 7 特殊版本(AdapterCleanupOrphanedDevicesWin7):

  • 检查自定义属性DEVPKEY_Wintun_OwningProcess,若进程不存在或进程创建时间不匹配,则判定为孤儿并删除。

此外,AdapterCleanupLegacyDevices用于清理早期 Wintun 版本在ROOT\NET枚举器下遗留的设备。


四、辅助函数与注册表操作

4.1PopulateAdapterData

从设备驱动注册表项(DIREG_DRV)读取:

  • NetCfgInstanceId:转换为 GUID 存入CfgInstanceID
  • NetLuidIndex:用于构造NET_LUID
  • *IfType:接口类型。

4.2AdapterGetDeviceObjectFileName

通过CM_Get_Device_Interface_ListW获取设备接口的符号链接名(如\\.\Wintun_{GUID}),用于后续CreateFile打开设备对象。

4.3WaitForInterface

使用 Windows 8 引入的DevCreateObjectQuery创建设备查询,等待设备接口启用,超时 15 秒。若超时或失败,记录详细错误信息(包括问题代码和 NTSTATUS)。

4.4 名称冲突解决(NciSetAdapterName

调用NciSetConnectionName尝试设置网络连接名,若返回ERROR_DUP_NAME,则自动在名称后添加空格加数字序号(最多尝试 1000 次),并尝试重命名冲突的现有连接。


五、总结

本文详细剖析了 Wintun 适配器的创建、打开、关闭、删除流程,以及驱动安装/卸载的完整机制。关键亮点包括:

  • 跨版本兼容性:为 Windows 7 保留传统 Setup API 路径,同时为 Windows 8+ 使用更现代的软件设备模型。
  • WOW64 透明代理:自动检测进程位数,通过rundll32启动代理 DLL 执行需要本机架构的 Setup API 调用。
  • 驱动版本管理:智能比较现有驱动与内置驱动版本,必要时升级并禁用/重新启用现有适配器。
  • 孤儿设备清理:确保崩溃进程留下的适配器能被自动回收,保持系统整洁。

下一篇文章将转向会话管理与数据路径,揭示 Wintun 高效收发的核心——环形缓冲区与无锁设计。