vdi-cloud/docs/usb-any-storage-20261104.md

5.2 KiB
Raw Blame History

任意 U 盘透传实现(2026-11-04)

在已验证的真实 USB 透传链路上,将盒子端策略从固定 VID/PID/序列号扩展为 “任意纯 USB Mass Storage 设备”。键盘、鼠标、Hub 和显示设备仍由盒子本地使用。

行为

  • 一个盒子、一个 VM 同时只导出一个存储设备;多个 U 盘同时插入时按盒子扫描顺序使用, 当前设备拔出后再接入下一个。
  • 设备必须是纯存储设备,所有 USB interface class 都必须为 08;键鼠、Hub 和复合设备拒绝。
  • 盒子检查分区是否本地挂载、是否有 holder 或作为 swap;发现本地占用时等待,不强制卸载。
  • QEMU 使用 0x08:-1:-1:-1:1 过滤器,允许任意 USB Mass Storage,仍拒绝其他 USB 类别。
  • PVE QMP watcher 在 VM/PVE 重启后重新建立运行时 usb-redir 设备。

部署入口

python tools/deploy-usb-device-poc.py \
  --pve PVE_HOST --box armbian@BOX_HOST --vm 100 --any-storage

部署前应在 Windows 中安全弹出当前 U 盘。部署器会安装盒子监控服务、USB 隧道和 vdi-usb-qmp-100.service;不会修改 VM 固定配置、GPU、显示或键鼠输入链路。

旧的精确设备策略仍可用:

python tools/deploy-usb-device-poc.py \
  --pve PVE_HOST --box armbian@BOX_HOST --vm 100 \
  --vendor 0951 --product 1666 --serial SERIAL

验收

至少用三只不同品牌或不同 VID/PID 的 U 盘测试:插入后 Windows 出现真实 USB Mass Storage 和盘符,读写大文件后校验 SHA-256;再测试安全弹出、拔插、 两个 U 盘同时插入,以及 VM/PVE 重启后的自动恢复。当前 Rust 单元测试 10/10、 Python QMP 测试 4/4 通过。已现场验证另一只 SanDisk 0781:5591 在 Windows 识别为 USBSTOR,F: NTFS 数据分区和 G: FAT32 UQITONG_ESP 分区均 Healthy。 多盘吞吐和 VM/PVE 重启后的恢复仍待验收;QMP watcher 已实现,但尚未现场安装。

插入体验优化

2026-11-04 已将当前 Windows 用户 test 的 StorageOnArrival 从 MSOpenFolder 改为 MSPromptEachTime,并保留自动播放开启。双分区装机盘仍显示两个卷, 不会再按“打开文件夹”这个默认操作自动打开两个窗口;提示数量和通知外观由 Windows 决定。

脚本 tools/set-usb-experience.ps1 可重复 Apply,并支持 Restore;仅修改三项 自动播放首选项,使用首次运行保存的原值回退。Windows 已执行 Apply 并回读成功。 备份路径:C:\ProgramData\VDI\usb-experience-20261104\preferences.clixml。

盒子监控程序使用已处理的 USB udev 事件即时唤醒,替代原来的每 2 秒轮询; 500ms 兜底扫描负责启动时已有设备和事件监听不可用的情况。活动会话直接核对 已导出的 sysfs 设备身份,不再因另一个 USB 设备扫描失败而误断开。 快速插盘时须等 bNumInterfaces 声明的所有接口出现后才做存储类型判断, 防止接口尚未枚举完整的存储/HID 复合设备被误接入。 日志包含 usb_time_ms、local_check_ms、since_latest_usb_event_ms;最后一项 仅为最近事件至导出调用的时间,不代表从物理插入至 Windows 完全可用的总时间。

盒子已离线测试并编译、部署(更新时无 U 盘连接),服务 active,udevadm 子进程正常。 旧二进制备份在 /home/armbian/vdi-terminal/backups/usb-events-20261104-110747/。 未重启 VM、Moonlight、Sunshine、Explorer,未修改启动分区和系统原生安全删除行为。 随后实测事件至导出调用为 5~8ms(仅盒子内部时间)。

继续排查发现发行版 usbredirect 0.13.0 的连接回调无条件订阅 G_IO_OUT, 现场导出进程曾占约 96% 的一个 CPU 核心。官方 0.15.0 的 update_watch() 按 usbredirhost_has_data_to_write() 动态监听;已从官方发布包编译私有版本:

  • 源码:https://www.spice-space.org/download/usbredir/usbredir-0.15.0.tar.xz
  • 源码 SHA256:6dc2a380277688a068191245dac2ab7063a552999d8ac3ad8e841c10ff050961
  • 私有程序:/usr/local/libexec/vdi-usbredirect
  • 二进制 SHA256:58b58b9d9ab017d3221e44f83c0794ae2977ef9e020fcb8df959eef997ae8e4a
  • 构建脚本:tools/build-usbredirect-private.sh;头文件仅下载并解包,没有安装或升级系统 GLib/libusb。
  • 盒子配置:/etc/vdi-usb-monitor/device.env 新增 VDI_USBREDIRECT_BINARY=/usr/local/libexec/vdi-usbredirect。
  • 切换前备份:/home/armbian/vdi-terminal/backups/usb-exporter-20261104-112031/。

运行时 CPU 实测降至约 1%。官方 filter 29 项、serializer 1 项测试全部通过; Rust 10 项测试通过。Windows 自动播放设置已回读验证,PowerShell 语法检查通过。 用户第一次复测仍反馈菜单慢,后续保持连接并再测后明确反馈“现在已经很快了”。 已确认不再自动打开文件夹;右下角提示和弹出菜单速度通过本次用户现场验收, 但没有测量从物理插入至菜单可用的完整耗时,也不代表长时间稳定性验收完成。

回退时先在 Windows 安全弹出并物理拔下 U 盘,再停止盒子 USB 服务, 从上述备份恢复二进制和 device.env,启动服务即可。Windows 自动播放可用 C:\ProgramData\VDI\usb-experience-20261104\set-usb-experience.ps1 -Action Restore 回退。