vdi-cloud/docs/usb-device-poc-20261103.md

118 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)