windows 驱动实例分析系列: wintun驱动分析-api篇(二)
Wintun API 模块深度解析(文档二):适配器生命周期与驱动管理
一、概述
本文档深入分析api模块中的适配器管理和驱动安装逻辑,主要涉及文件:
adapter.c/adapter.hadapter_win7.hdriver.c/driver.hregistry.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中被调用(首次创建适配器时)。流程如下:
- 获取互斥锁:通过
NamespaceTakeDriverInstallationMutex()确保同一时刻只有一个进程执行安装,避免竞争。 - 创建设备信息:使用
SetupDiCreateDeviceInfoListExW创建网络设备类的一个临时设备信息元素,设置硬件 ID 为Wintun。 - 枚举已安装的兼容驱动:通过
SetupDiBuildDriverInfoList获取系统中所有匹配Wintun硬件 ID 的驱动。 - 版本比较:遍历每个驱动信息,与 Wintun 内置驱动版本(从 INF 提取的
WINTUN_INF_VERSION和WINTUN_INF_FILETIME)比较。如果现有驱动更新,则直接使用;如果内置驱动更新,则:- 禁用所有使用当前驱动创建的 Wintun 适配器(调用
DisableAllOurAdapters),等待驱动卸载(EnsureWintunUnloaded)。 - 调用
SetupUninstallOEMInfW卸载旧驱动(SUOI_FORCEDELETE强制删除)。 - 继续寻找下一个驱动。
- 禁用所有使用当前驱动创建的 Wintun 适配器(调用
- 若没有找到可用驱动,则执行全新安装:
- 使用
ResourceCreateTemporaryDirectory在%WINDIR%\Temp下创建随机临时目录。 - 根据本机架构(
NativeMachine),从 DLL 资源中提取对应的wintun.sys、wintun.cat、wintun.inf到临时目录。 - 调用
SetupCopyOEMInfW将 INF 复制到系统驱动存储(%SystemRoot%\INF),完成安装。 - 删除临时文件并移除目录。
- 使用
- 保存上下文:将之前禁用的适配器列表通过输出参数
DevInfoExistingAdaptersForCleanup和ExistingAdaptersForCleanup返回,由调用方在适配器创建完成后重新启用它们(通过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+ 的软件设备创建路径
步骤:
- 生成实例 ID:若调用方提供了
RequestedGUID,则直接使用;否则调用CoCreateGuid生成随机 GUID。将 GUID 转换为字符串作为设备实例 ID。 - 创建存根设备(Stub):在 Windows 10 上,先通过
SwDeviceCreate创建一个临时的存根设备,仅设置硬件 ID 为空字符串,并在其软件注册表项中写入SuggestedInstanceId(用于影响 NLA GUID)。然后立即关闭该存根。- 此步骤的目的是为系统网络位置感知(NLA)提供确定的 GUID,避免每次创建新适配器都生成新的 NLA 条目。
- 创建主设备:再次调用
SwDeviceCreate,传入实际的硬件 ID(WINTUN_HWID)和设备属性:DEVPKEY_Wintun_Name:用户指定的适配器名称(如 “Demo”)。DEVPKEY_Device_FriendlyName:隧道类型名称 + " Tunnel"。DEVPKEY_Device_DeviceDesc:同上。- 设置标志
SWDeviceCapabilitiesSilentInstall(静默安装)和SWDeviceCapabilitiesDriverRequired(需要驱动)。
- 等待设备创建完成:通过回调
DeviceCreateCallback设置事件,然后等待。 - 等待接口可用:调用
WaitForInterface,使用DevCreateObjectQuery查询设备接口(GUID_DEVINTERFACE_NET)是否已启用,超时 15 秒。这确保了驱动已正确加载并注册了设备接口。 - 打开设备信息:通过
SetupDiOpenDeviceInfo打开该设备,然后调用PopulateAdapterData从注册表读取NetCfgInstanceId、NetLuidIndex、*IfType等关键信息。 - 设置网络连接名称:调用
NciSetAdapterName(内部使用NciSetConnectionName)将网络连接显示名设置为用户指定的名称,若冲突则自动添加序号后缀。 - Windows 7 特殊处理:若系统为 Windows 7,则路径不同(见下文)。
3.3 Windows 7 的专用创建路径
Windows 7 不支持SwDeviceCreate,因此 Wintun 使用传统的 Setup API 进行设备创建(参考adapter_win7.h中的CreateAdapterWin7函数):
- 创建设备信息元素,设置硬件 ID 为
Wintun。 - 构建兼容驱动列表,选中第一个匹配的驱动。
- 依次调用
DIF_REGISTERDEVICE、DIF_REGISTER_COINSTALLERS、DIF_INSTALLINTERFACES、DIF_INSTALLDEVICE。 - 设置自定义属性
DEVPKEY_Wintun_OwningProcess(包含进程 ID 和创建时间),用于孤儿设备清理。 - 等待设备接口可用(通过轮询检查注册表值和设备状态)。
- 返回设备实例 ID。
3.4 WOW64 代理调用
在 32 位进程运行于 64 位系统上时,直接调用 Setup API 会失败。因此,adapter.c中的AdapterRemoveInstance、AdapterEnableInstance、AdapterDisableInstance等函数会检查全局NativeMachine和IMAGE_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_Status的DN_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 高效收发的核心——环形缓冲区与无锁设计。
