# U 盘真实 USB 设备透传(2026-11-03) 用户明确选择 Windows 必须识别真实 USB,并配合插盘、换口和 Windows 安全弹出后实际拔插。 当前为一个盒子、一个登记设备、一个 VM 的 POC,不是多用户生产功能。 ## 已验证的现场结果 | 项目 | 结果 | | --- | --- | | U 盘 | Kingston DataTraveler 3.0,0951:1666,USB 描述符序列号 408D5C1653D4E44179789251 | | 盒子 | 192.168.1.188,物理路径 3-1,实际 USB2 / 480 Mb/s 链路 | | Windows | PVE starhe / VM100 / 192.168.1.70,USB Mass Storage 状态 OK | | 磁盘 | Kingston DataTraveler 3.0 USB Device,InterfaceType=USB,USBSTOR 实例含原序列号 | | 卷 | KINGSTON(F:),FAT32,Removable,约 57.7 GiB,Healthy | | 文件读写 | 1 MiB 随机文件:源文件、USB 文件、复制回 VM 的文件 SHA-256 一致 | | 实物拔插 | 用户先在 Windows 安全弹出,17:34:43 断开、17:34:53 重新导出,Windows F: 自动恢复 | | 拔插后数据 | 再次读取测试文件,SHA-256 不变 | | 独占 | 盒子 usbfs 持有接口;透传时 Linux 无该 U 盘的本地块设备挂载 | | 原有会话 | Moonlight PID46934 / 2K45 保留,显示守护 active,GTX965M 错误码 0 | 只新增 `F:\VDI_USB_TEST_20261103-173342\from-windows.bin` 和 `readme.txt`。 未格式化、改分区、覆盖或删除原文件。源和回读副本在 VM 的 `C:\Windows\TEMP\VDI_USB_TEST_20261103-173342`。 SHA-256:`8244F1189EFA02118BF0DC4CB68FC8924FE562AC610B68DEEFBE09EC85454D51`。 原始验收记录见 [`evidence/usb-device-20261103/`](evidence/usb-device-20261103/)。 自动检查:盒子 Rust 测试 5/5、管理员 QMP 测试 3/3,通过;Python 编译检查和 `git diff --check` 通过。现场还实测了“本地挂载时拒绝导出”,随后正常卸载才导出。 ## 实现 ```text 盒子物理 USB -> Rust vdi-usb-monitor -> usbredirect/libusb(回环40001) -> PVE 发起的 SSH 加密隧道 -> QEMU usb-redir(现有 ehci-2.0) -> Windows 自带 USB Mass Storage / USBSTOR -> F: ``` usbredir 独立于 SPICE;显示通道仍为 Moonlight/Sunshine。 没有安装 Windows 第三方驱动、开测试签名、替换控制器、重启机器或改 GPU 配置。 盒子不调用 PVE API,也不保存 PVE 管理凭据。 Rust 使用标准库,无第三方 crate;等待设备每 2 秒扫描,活动连接每秒检查身份与地址。 按 VID/PID/serial 匹配唯一设备,并使用当前 bus-address 打开精确实例。 设备消失、匹配多实例、含非存储接口、已本地挂载、存在块设备 holder 或被用作 swap 时拒绝。 udev 规则仅对登记设备设置 UDISKS_IGNORE=1,防止本地自动挂载与 Windows 并发写盘。 SSH 使用独立密钥、已有可信主机密钥、源 IP 和 permitopen 限制,禁止该密钥执行 shell。 USB 监听与隧道两端均为回环地址,未暴露 LAN USB 服务端口。 Rust 为终端模块;Python 是管理员部署/QMP 工具;PowerShell 是 Windows 验收工具。 ## 部署和管理 盒子先安装 `rustc cargo usbredirect`,在 `/home/armbian/vdi-build/usb-monitor` 运行 `cargo test --offline`、`cargo build --release --offline`。没有整体升级系统。 管理员机器需要 `paramiko`、可信 SSH 主机密钥,以及 PVE SSH 公钥或 `VDI_PVE_SSH_PASSWORD` 环境变量。部署器不保存密码。 ```sh python tools/deploy-usb-device-poc.py --pve 192.168.1.51 --box armbian@192.168.1.188 \ --vm 100 --vendor 0951 --product 1666 --serial 408D5C1653D4E44179789251 ``` 确认设备身份、关闭本地文件操作并正常卸载对应分区,然后启动盒子服务: `sudo systemctl start vdi-usb-monitor.service`。本次分区为 `/dev/sda1`, 使用了 `udisksctl unmount --block-device /dev/sda1`;其他环境不能盲目照抄设备名。 在 PVE root 执行: ```sh python3 /usr/local/libexec/vdi-usb-device-qmp.py attach --vm 100 --vendor 0951 --product 1666 python3 /usr/local/libexec/vdi-usb-device-qmp.py status --vm 100 ``` QMP 只管理 `vdi_usb_box_100` / `vdi_usb_redir_100`,不操作已有 tablet、GPU、网卡。 重复 attach 拒绝覆盖;device_add 失败回收仅本次新建 channel。 QEMU filter 内部分隔符为冒号,与 spice-client 的逗号不同。 | 位置 | 文件 | | --- | --- | | 盒子 | `/usr/local/libexec/vdi-usb-monitor` | | 盒子白名单 | `/etc/vdi-usb-monitor/device.env` | | 盒子挂载策略 | `/etc/udev/rules.d/99-vdi-usb-device-poc.rules` | | 盒子服务 | `/etc/systemd/system/vdi-usb-monitor.service` | | PVE 密钥 | `/etc/vdi-usb-poc/vm100/` | | PVE 隧道 | `/etc/systemd/system/vdi-usb-tunnel-100.service` | | 盒子原始备份 | `/home/armbian/vdi-terminal/backups/usb-device-20261103-172709/` | 两端服务已 active/enabled,未通过重启验证开机顺序。重新部署前须在 Windows 安全弹出, 停止盒子导出服务;部署器拒绝覆盖运行中的服务。 ## 限制 - 仅登记的这一只 U 盘;其他设备需管理员登记。没有多终端隔离、会话授权/切换或 Qt 状态提示。 - 固定 IP、单端口、单 VM POC。QMP 设备仅存在于当前运行实例,VM/PVE 重启后须重新 attach; 没有修改原 VM args 或 GPU 启动设置。 - 验证了当前 FAT32 U 盘、1 MiB 读写和一次实物热插拔;未测大文件吞吐、NTFS/exFAT、 写入中断网/掉电恢复或长期老化。 - 最初接口报 USB 枚举 -71,换到 3-1 对应口才成功;并非软件修好了原口,也未验证 USB3 速度。 - 使用后正常安全弹出;程序不会重试用户文件写入,不能保证强拔/断网时的数据完整性。 ## 回退 1. 关闭 U 盘文件,在 Windows 正常安全弹出。 2. PVE:`python3 /usr/local/libexec/vdi-usb-device-qmp.py detach --vm 100 --guest-ejected`。 3. 盒子:`sudo systemctl disable --now vdi-usb-monitor.service`;PVE: `systemctl disable --now vdi-usb-tunnel-100.service`。 4. 仅移除/恢复本功能 udev 规则,`udevadm control --reload-rules`,再拔插让 Linux 本地重新识别。 5. 如移除密钥,只删 authorized_keys 中 `vdi-usb-poc-vm100` 对应公钥行;保留其他公钥, 不直接覆盖整份可能已经更新的 authorized_keys。 ## 官方依据 - [usbredir 独立协议](https://www.spice-space.org/usbredir.html) - [Ubuntu usbredirect](https://manpages.ubuntu.com/manpages/noble/man1/usbredirect.1.html) - [QEMU USB 接入](https://www.qemu.org/docs/master/system/devices/usb.html) - [QEMU filter 实现](https://github.com/qemu/qemu/blob/master/hw/usb/redirect.c)