WinDivert 驱动问题排查指南

windivert: https://www.reqrypt.org/windivert-doc.html

目录

  1. 问题概述
  2. WinDivert 工作原理
  3. 错误码速查表
  4. 排查流程
  5. 常见问题与解决方案
  6. 预防措施
  7. 相关代码引用

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.sys64位内核驱动必须与 DLL 同目录
WinDivert32.sys32位内核驱动32位应用需要
WinDivert.lib链接库仅编译时需要

⚠️ 关键: .sys 驱动文件必须与 WinDivert.dll同一目录,因为 DLL
使用 GetModuleFileName() 获取自身路径后在同目录查找驱动。


3. 错误码速查表

错误码常量名含义常见原因
2ERROR_FILE_NOT_FOUND驱动文件未找到.sys 不在 DLL 同目录;服务指向旧路径
5ERROR_ACCESS_DENIED权限不足未以管理员身份运行
87ERROR_INVALID_PARAMETER参数无效filter 语法错误;layer/priority/flags 无效
577ERROR_INVALID_IMAGE_HASH签名无效驱动未正确签名;Secure Boot 问题
654ERROR_DRIVER_BLOCKED版本不兼容已加载不同版本的驱动
1060ERROR_SERVICE_DOES_NOT_EXIST服务不存在使用了 NO_INSTALL 标志但驱动未预装
1257ERROR_DRIVER_BLOCKED驱动被阻止安全软件拦截;虚拟机不支持
1753ERROR_SERVICE_DISABLEDBFE 服务禁用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 阻止驱动加载。

解决方案:

  1. 将项目目录添加到杀毒软件白名单
  2. 临时禁用实时保护测试
  3. 确保使用官方签名的驱动文件

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

排查过程:

  1. 检查文件存在性 ✓ - 文件都在 D:\...\target\debug\
  2. 检查服务状态:
    sc.exe query WinDivert
    STATE: 1 STOPPED
    WIN32_EXIT_CODE: 2  ← 文件找不到!
    
  3. 检查驱动路径:
    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 会从新路径自动安装驱动