windivert-troubleshooting
WinDivert 驱动问题排查指南
windivert: https://www.reqrypt.org/windivert-doc.html
目录
1. 问题概述
1.1 典型错误信息
WARN hornet_tun::routing::windows::windivert::socket_monitor:
Failed to create WinDivert SOCKET handle: SYS driver file not found
WARN hornet_tun::routing::windows::windivert::socket_monitor:
SocketMonitor loop error: Windows routing API failed:
Failed to open WinDivert SOCKET handle: SYS driver file not found
1.2 问题本质
WinDivert 是一个 Windows 内核驱动 + 用户态 DLL 的组合。当 WinDivertOpen()
被调用时,DLL 会尝试加载对应的 .sys 驱动文件。如果找不到驱动文件,就会返回
ERROR_FILE_NOT_FOUND (错误码 2)。
2. WinDivert 工作原理
2.1 架构概览
┌─────────────────────────────────────────────────────────────────┐
│ 用户态 (User Mode) │
│ ┌──────────────────┐ ┌──────────────────────────────────┐ │
│ │ client-daemon │───▶│ WinDivert.dll │ │
│ │ (Rust App) │ │ - WinDivertOpen() │ │
│ └──────────────────┘ │ - WinDivertRecv() │ │
│ │ - WinDivertSend() │ │
│ └───────────────┬──────────────────┘ │
├──────────────────────────────────────────┼──────────────────────┤
│ 内核态 (Kernel Mode) │
│ ┌───────────────▼──────────────────┐ │
│ │ WinDivert64.sys │ │
│ │ - 网络包拦截 │ │
│ │ - Socket 事件监控 │ │
│ │ - Flow 跟踪 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 驱动加载流程
WinDivertOpen() 被调用
│
▼
┌───────────────────────┐
│ 1. 检查 WinDivert 服务 │
│ 是否已存在 │
└───────────┬───────────┘
│
┌──────┴──────┐
│ │
▼ 存在 ▼ 不存在
┌─────────────┐ ┌─────────────────────────┐
│ 2a. 尝试启动│ │ 2b. 安装新驱动 │
│ 服务 │ │ - 获取 DLL 所在目录 │
└──────┬──────┘ │ - 查找 WinDivert64.sys │
│ │ - 调用 SCM 安装服务 │
│ └───────────┬─────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ 3. 加载驱动到内核 │
│ - 验证数字签名 │
│ - 初始化 WFP callout │
└───────────────────┬─────────────────────┘
│
┌────────┴────────┐
│ │
▼ 成功 ▼ 失败
┌────────────┐ ┌────────────────┐
│ 返回句柄 │ │ 返回错误码 │
│ HANDLE │ │ (见错误码表) │
└────────────┘ └────────────────┘
2.3 文件依赖关系
| 文件 | 说明 | 位置要求 |
|---|---|---|
WinDivert.dll | 用户态库 | 与 .exe 同目录或 PATH 中 |
WinDivert64.sys | 64位内核驱动 | 必须与 DLL 同目录 |
WinDivert32.sys | 32位内核驱动 | 32位应用需要 |
WinDivert.lib | 链接库 | 仅编译时需要 |
⚠️ 关键:
.sys驱动文件必须与WinDivert.dll在同一目录,因为 DLL
使用GetModuleFileName()获取自身路径后在同目录查找驱动。
3. 错误码速查表
| 错误码 | 常量名 | 含义 | 常见原因 |
|---|---|---|---|
| 2 | ERROR_FILE_NOT_FOUND | 驱动文件未找到 | .sys 不在 DLL 同目录;服务指向旧路径 |
| 5 | ERROR_ACCESS_DENIED | 权限不足 | 未以管理员身份运行 |
| 87 | ERROR_INVALID_PARAMETER | 参数无效 | filter 语法错误;layer/priority/flags 无效 |
| 577 | ERROR_INVALID_IMAGE_HASH | 签名无效 | 驱动未正确签名;Secure Boot 问题 |
| 654 | ERROR_DRIVER_BLOCKED | 版本不兼容 | 已加载不同版本的驱动 |
| 1060 | ERROR_SERVICE_DOES_NOT_EXIST | 服务不存在 | 使用了 NO_INSTALL 标志但驱动未预装 |
| 1257 | ERROR_DRIVER_BLOCKED | 驱动被阻止 | 安全软件拦截;虚拟机不支持 |
| 1753 | ERROR_SERVICE_DISABLED | BFE 服务禁用 | Base Filtering Engine 被禁用 |
对应的 Rust 错误类型定义 (来自 crates/windivert-rust/windivert/src/error.rs):
#[derive(Debug, Error)]
pub enum WinDivertOpenError {
#[error("SYS driver file not found")]
MissingSYS, // 错误码 2
#[error("Running without elevated access rights")]
AccessDenied, // 错误码 5
#[error("Invalid parameter (filter string, layer, priority, or flags)")]
InvalidParameter, // 错误码 87
#[error("SYS driver file has invalid digital signature")]
InvalidImageHash, // 错误码 577
#[error("An incompatible version of the WinDivert driver is currently loaded")]
IncompatibleVersion, // 错误码 654
#[error("WinDivert driver is blocked by security software...")]
DriverBlocked, // 错误码 1257
#[error("Base Filtering Engine service has been disabled")]
BaseFilteringEngineDisabled, // 错误码 1753
}
4. 排查流程
排查流程图
┌─────────────────────────┐
│ 错误: SYS driver file │
│ not found │
└───────────┬─────────────┘
│
┌───────────▼───────────┐
│ Step 1: 检查文件存在性 │
└───────────┬───────────┘
│
┌───────────▼───────────┐
┌────────┤ 文件是否在 target/ │
│ │ debug 目录? │
│ └───────────┬───────────┘
│ │
否 │ 是 │
▼ ▼
┌──────────────┐ ┌────────────────────┐
│ 复制文件 │ │ Step 2: 检查服务 │
│ (见 5.1) │ │ 状态 │
└──────────────┘ └─────────┬──────────┘
│
┌────────────▼────────────┐
│ sc.exe query WinDivert │
└────────────┬────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
服务不存在 服务已停止 服务运行中
│ (EXIT_CODE=2) │
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 确认文件位置 │ │ Step 3: 检查 │ │ 版本可能不匹配│
│ 以管理员运行 │ │ 驱动路径 │ │ 需要重装 │
└───────────────┘ └───────┬───────┘ └───────────────┘
│
┌───────────▼───────────┐
│ sc.exe qc WinDivert │
│ 检查 BINARY_PATH_NAME │
└───────────┬───────────┘
│
┌───────────▼───────────┐
否 │ 路径是否指向正确的 │ 是
┌────────┤ .sys 文件? ├────────┐
│ └──────────────────────┘ │
▼ ▼
┌──────────────────┐ ┌──────────────┐
│ Step 4: 删除旧 │ │ 检查权限或 │
│ 服务并重装 │ │ 其他问题 │
└──────────────────┘ └──────────────┘
Step 1: 检查文件是否存在
# 检查 target/debug 目录中的 WinDivert 文件
Get-ChildItem "D:\proj\rs\abc\target\debug\WinDivert*"
# 预期输出:
# WinDivert.dll (约 47KB)
# WinDivert.lib (约 25KB)
# WinDivert64.sys (约 94KB)
如果文件不存在,参考
5.1 文件缺失。
Step 2: 检查 WinDivert 服务状态
# 查询服务状态
sc.exe query WinDivert
# 可能的输出:
# STATE: 4 RUNNING -> 服务正在运行
# STATE: 1 STOPPED -> 服务已停止
# WIN32_EXIT_CODE: 2 -> 驱动文件找不到(关键!)
Step 3: 检查驱动文件路径
# 查询服务配置,特别是 BINARY_PATH_NAME
sc.exe qc WinDivert
# 示例输出:
# BINARY_PATH_NAME: \??\D:\proj\rs\abc\target\debug\WinDivert64.sys
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# 这个路径必须指向实际存在的 .sys 文件!
Step 4: 检查环境变量
# 检查编译时使用的 WINDIVERT_PATH
$env:WINDIVERT_PATH
# 检查系统级环境变量
[System.Environment]::GetEnvironmentVariable("WINDIVERT_PATH", "Machine")
Step 5: 检查 PATH 中的 WinDivert
# 检查 PATH 中是否有其他 WinDivert.dll
where.exe WinDivert.dll
# 如果有多个结果,可能导致加载错误的 DLL
5. 常见问题与解决方案
5.1 文件缺失:WinDivert.dll 或 WinDivert64.sys 不存在
原因: 编译后未将运行时依赖复制到目标目录。
解决方案:
# 方法 1: 使用项目提供的脚本
.\scripts\ps1\copy-runtime-dlls.ps1
# 方法 2: 手动从 sidecar 目录复制
$src = "D:\proj\rs\abc\crates\abc-tun\sidecar\windows\windivert\x64"
$dst = "D:\proj\rs\abc\target\debug"
Copy-Item "$src\WinDivert.dll" $dst
Copy-Item "$src\WinDivert.lib" $dst
Copy-Item "$src\WinDivert64.sys" $dst
5.2 服务路径指向错误位置(最常见!)
典型场景: 在不同盘符/目录编译运行过项目,Windows SCM 记住了旧路径。
诊断:
# 查询当前服务指向的驱动路径
sc.exe qc WinDivert
# 如果 BINARY_PATH_NAME 指向一个不存在的路径(如旧的 Y: 盘),就是这个问题
解决方案:
# 以管理员身份删除旧服务
sc.exe delete WinDivert
# 验证服务已删除
sc.exe query WinDivert
# 应该显示: "The specified service does not exist"
# 重新以管理员身份运行程序,WinDivert 会自动重新安装
5.3 权限不足 (ERROR_ACCESS_DENIED)
原因: WinDivert 需要管理员权限来安装/加载内核驱动。
解决方案:
# 方法 1: 右键 -> 以管理员身份运行
# 方法 2: 管理员 PowerShell
Start-Process -FilePath ".\target\debug\client-daemon.exe" -Verb RunAs
5.4 版本不兼容 (ERROR_DRIVER_BLOCKED / 654)
原因: 系统中已加载不同版本的 WinDivert 驱动。
解决方案:
# 1. 停止所有使用 WinDivert 的进程
Get-Process | Where-Object { $_.Modules.ModuleName -contains "WinDivert.dll" } | Stop-Process
# 2. 停止并删除服务
sc.exe stop WinDivert
sc.exe delete WinDivert
# 3. 重启系统(可选,确保驱动完全卸载)
# 4. 重新运行程序
5.5 安全软件阻止 (ERROR_DRIVER_BLOCKED / 1257)
原因: 杀毒软件或 Windows Defender 阻止驱动加载。
解决方案:
- 将项目目录添加到杀毒软件白名单
- 临时禁用实时保护测试
- 确保使用官方签名的驱动文件
5.6 Base Filtering Engine 被禁用 (1753)
原因: Windows BFE 服务被禁用,WinDivert 依赖此服务。
解决方案:
# 检查 BFE 服务状态
sc.exe query BFE
# 启动 BFE 服务
sc.exe config BFE start= auto
sc.exe start BFE
6. 预防措施
6.1 使用固定的全局安装路径
为避免多项目/多盘符切换导致的问题,建议使用固定路径:
# 创建全局目录
New-Item -ItemType Directory -Force -Path "C:\ProgramData\WinDivert"
# 复制文件
Copy-Item ".\sidecar\windows\windivert\x64\*" "C:\ProgramData\WinDivert\"
# 设置系统环境变量(需要管理员权限)
[System.Environment]::SetEnvironmentVariable(
"WINDIVERT_PATH",
"C:\ProgramData\WinDivert",
"Machine"
)
6.2 编译后自动复制脚本
在 Cargo.toml 或 build 脚本中添加后处理步骤,确保每次编译后文件就位。
6.3 添加运行时检查
在程序启动时添加诊断日志:
// 建议在 main() 或初始化时添加
fn check_windivert_prerequisites() -> Result<(), String> {
let exe_dir = std::env::current_exe()
.map_err(|e| format!("无法获取 exe 路径: {}", e))?
.parent()
.ok_or("无法获取 exe 目录")?
.to_path_buf();
let dll_path = exe_dir.join("WinDivert.dll");
let sys_path = exe_dir.join("WinDivert64.sys");
if !dll_path.exists() {
return Err(format!("WinDivert.dll 不存在: {:?}", dll_path));
}
if !sys_path.exists() {
return Err(format!("WinDivert64.sys 不存在: {:?}", sys_path));
}
Ok(())
}
7. 相关代码引用
7.1 WinDivert 调用入口
文件: crates/abc-tun/src/routing/windows/windivert/socket_monitor.rs
// 第 70-86 行: WinDivert 句柄创建
let socket_handle = match WinDivert::socket(
"tcp || udp", // filter: 捕获 TCP 和 UDP
1041, // priority
WinDivertFlags::new().set_recv_only().set_sniff(), // 只读 + 嗅探模式
) {
Ok(handle) => {
info!("WinDivert SOCKET handle created successfully");
handle
}
Err(e) => {
warn!("Failed to create WinDivert SOCKET handle: {}", e);
return Err(crate::routing::Error::WindowsApi(format!(
"Failed to open WinDivert SOCKET handle: {}",
e
)));
}
};
7.2 编译时配置
文件: crates/abc-tun/Cargo.toml
# Windows + 非 MSVC(gnu)- 使用静态链接
[target.'cfg(all(windows, not(target_env = "msvc")))'.dependencies]
windivert = { path = "../windivert-rust/windivert", features = ["static"] }
# Windows + MSVC - 使用动态链接
[target.'cfg(all(windows, target_env = "msvc"))'.dependencies]
windivert = { path = "../windivert-rust/windivert" }
7.3 Build 脚本关键逻辑
文件: crates/windivert-rust/windivert-sys/build/main.rs
// 环境变量定义
pub const LIB_PATH_ARG: &str = "WINDIVERT_PATH";
pub const DLL_OUTPUT_PATH_ARG: &str = "WINDIVERT_DLL_OUTPUT";
pub const STATIC_BUILD_ARG: &str = "WINDIVERT_STATIC";
// 构建模式选择(第 26-64 行)
if env::var(STATIC_BUILD_ARG).is_ok() || cfg!(feature = "static") {
// 静态编译模式
compile::lib();
println!("cargo:warning=WinDivert{arch}.sys must be located in the same path as the executable.")
} else if let Ok(lib_path) = env::var(LIB_PATH_ARG) {
// 使用预编译的 DLL(从 WINDIVERT_PATH 复制)
println!("cargo:rustc-link-lib=dylib=WinDivert");
handle_provided_dll(arch, &out_dir, &lib_path);
} else if cfg!(feature = "vendored") {
// 从源码编译 DLL
compile::dll();
} else {
panic!("...");
}
7.4 错误码映射
文件: crates/windivert-rust/windivert/src/error.rs
// 第 66-82 行: Windows 错误码到 Rust 枚举的映射
impl TryFrom<i32> for WinDivertOpenError {
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
2 => Ok(WinDivertOpenError::MissingSYS),
5 => Ok(WinDivertOpenError::AccessDenied),
87 => Ok(WinDivertOpenError::InvalidParameter),
577 => Ok(WinDivertOpenError::InvalidImageHash),
654 => Ok(WinDivertOpenError::IncompatibleVersion),
1060 => Ok(WinDivertOpenError::MissingInstall),
1257 => Ok(WinDivertOpenError::DriverBlocked),
1753 => Ok(WinDivertOpenError::BaseFilteringEngineDisabled),
_ => Err(std::io::Error::from_raw_os_error(value)),
}
}
}
附录 A: 快速诊断命令清单
# === 文件检查 ===
# 检查 target/debug 目录
Get-ChildItem "target\debug\WinDivert*" | Format-Table Name, Length
# === 服务检查 ===
# 查询服务状态
sc.exe query WinDivert
# 查询服务配置(关键:检查 BINARY_PATH_NAME)
sc.exe qc WinDivert
# === 环境变量检查 ===
# 当前会话
$env:WINDIVERT_PATH
# 系统级
[System.Environment]::GetEnvironmentVariable("WINDIVERT_PATH", "Machine")
# === PATH 检查 ===
where.exe WinDivert.dll
# === 修复命令 ===
# 删除旧服务(需要管理员权限)
sc.exe delete WinDivert
# 复制运行时文件
.\scripts\ps1\copy-runtime-dlls.ps1
附录 B: 案例分析
案例:跨盘符开发导致的驱动路径错误
背景: 用户在 Y 盘开发时编译运行过项目,后来切换到 D 盘继续开发。
现象:
WARN: Failed to create WinDivert SOCKET handle: SYS driver file not found
排查过程:
- 检查文件存在性 ✓ - 文件都在
D:\...\target\debug\ - 检查服务状态:
sc.exe query WinDivert STATE: 1 STOPPED WIN32_EXIT_CODE: 2 ← 文件找不到! - 检查驱动路径:
sc.exe qc WinDivert BINARY_PATH_NAME: \??\Y:\projs\rs\abc\target\debug\WinDivert64.sys ^ 指向 Y 盘旧路径!
根本原因: Windows SCM 记住了 Y 盘的驱动路径,但当前项目在 D 盘。
解决方案:
# 删除旧服务
sc.exe delete WinDivert
# 以管理员身份重新运行程序
# WinDivert 会从新路径自动安装驱动