usb-device-farm — 集群 USB 设备集中管理与租借平台
编排层实现(v0.5.0 / M1 + M2 + M2.5 + M2.6)。不重写 USB/IP 协议栈与内核驱动(ADR-01), 只复用 Linux
usbip/ Windowsusbipd-win+usbip-win2工具链,自建账号、目录、租约、抢占、审计、路径决策与中继鉴权。 PyPI 包名usb-device-farm(导入名仍为usbipd;命令入口usb-device-farm,避免与 usbipd-win 的usbipd.exe冲突)。
三点结论
- 零第三方依赖:Python 3.9+ 标准库(
sqlite3/http.server/asyncio/urllib)。项目用 uv 管理:uv run自动创建虚拟环境并执行;无 uv 的机器仍可python -m usbipd直接跑,无需 pip install。 - 默认
dry-run:整条链路(查询→租用→attach→抢占→收回→到期断链)都能在无真机环境跑通并验收,但不会改系统防火墙、不会复位真实 USB 设备;--live才真正调用现成工具。 - 强制断开是真断数据面:撤准入白名单 + relay 主动关闭长连接 + detach + 设备复位 + 审计,且抢占在单事务内原子转交,不留可被第三人抢走的空窗(E-16);断链校验失败设备置
fault(E-18)。
环境准备(uv)
# 需 uv ≥ 0.4(项目已含 .python-version=3.12 与 uv.lock)
uv sync # 创建 .venv 并以可编辑方式安装本项目(零第三方依赖)
uv run usb-device-farm --help
# 发布后可从 PyPI 安装:uv tool install usb-device-farm 或 pip install usb-device-farm
| 命令 | 作用 |
|---|---|
uv sync |
按 uv.lock 同步虚拟环境与本项目 |
uv run python -m usbipd ... |
在 .venv 中运行(下文示例均可加 uv run 前缀) |
uv run usb-device-farm ... |
同上,等价的安装后命令入口(名称避开 usbipd-win 的 usbipd) |
uv run python -m unittest discover -s tests -q |
运行 37 项单测 |
uv lock --upgrade |
升级锁文件(当前无第三方依赖,仅锁定本项目与 Python 版本) |
uv python install 3.12 |
无系统 Python 时由 uv 托管解释器 |
uv build / uv publish |
构建并发布 sdist+wheel 到 PyPI |
架构
flowchart LR
C[client CLI<br/>query / map / unmap] -- 控制面 HTTPS/JSON --> H[hub<br/>目录·账号·优先级·租约·审计]
A[node-agent<br/>采集·心跳·策略] -- 控制面 --> H
H -- connect_plan --> C
H -- 下发命令/复位 --> A
C -. 直连 direct: USB/IP 3240 .-> N[宿主 usbipd / usbipd-win]
C -. 不可达 relay: USB/IP 3240 .-> R[relay<br/>v1.1.1 协议感知]
R -. 长连接转发 .-> N
H -. 会话注册/主动断链 .-> R
hub 与 relay 目前同进程共享会话注册表(RelayRegistry);独立 relay 节点池是 Q-15 的后续工作。
快速开始(三终端,dry-run 演示)
以下命令均在 uv sync 之后执行(无 uv 时去掉 uv run 前缀即可):
# 1) 中心服务:首次启动自动生成管理员并打印一次性口令
# 加 --web 才启用浏览器控制台(默认关闭,纯 API)
USBIPD_BOOTSTRAP_PASSWORD=admin123 USBIPD_AGENT_TOKEN=secret \
uv run python -m usbipd hub --host 0.0.0.0 --port 8420 --relay-host <hub对外IP> --web
# 2) 宿主代理(无真机时 --mock 上报 2 台模拟设备)
USBIPD_AGENT_TOKEN=secret uv run python -m usbipd agent \
--hub http://<hub>:8420 --node-id node-1 --host <宿主IP> --mock --interval 5
# 跨网段不可直连时加 --unreachable,hub 会自动对该节点走 relay
# 3) 使用者电脑
uv run python -m usbipd client --hub http://<hub>:8420 --user admin --password admin123 \
admin user-add alice pass123 --priority 10
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 query
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 map <device_id>
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 status
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 unmap
map 默认只打印将要执行的 usbip attach -r <host> -b <busid>;加 --live 才真正执行,且直连失败会自动请求 hub 切换到 relay(Q-13 自动回退)。
Web 控制台(可选,--web 启用)
默认 不启用,hub 只提供 JSON API。加 --web(或 USBIPD_WEB=1)后访问 http://<hub>:<port>/:
| 角色 | 能做什么 |
|---|---|
| 用户 | 设备目录(按状态/关键词筛选)、租用与抢占、查看 connect_plan 与 attach 命令、我的租约(激活/续租/断开,剩余时间倒计时) |
| 管理员 | 上述全部 + 新建/停用账号、改优先级、节点列表、停/开共享、复位 fault、强制收回(原因写审计)、审计日志 |
单文件 usbipd/hub/static/index.html,原生 JS、无 CDN、无构建步骤;token 存 localStorage,页面每 5s 刷新,登出即清除。未启用时 GET / 与 /ui 返回 404 WEB_UI_DISABLED 并提示加 --web。
python -m usbipd hub --port 8420 --web # 浏览器打开 http://127.0.0.1:8420/
命令速查
| 组件 | 命令 | 说明 |
|---|---|---|
| hub | python -m usbipd hub --help |
中心服务;--web 开控制台、--no-relay / --force-relay / --live |
| agent | python -m usbipd agent --help |
--os auto/linux/windows、--mock、--once、--unreachable |
| client | query / map / unmap / renew / activate / switch-relay / status / login |
三动作 + 续租/回退 |
| client | `admin users | user-add |
| 自检 | uv run python -m usbipd selftest |
端到端 11 项冒烟,无需真机 |
| 单测 | uv run python -m unittest discover -s tests -v |
37 项,含真实 TCP 中继越权、非默认端口一致性、Windows 未知设备类禁抢、Web UI 启用/禁用 |
全局参数 --hub / --user / --password / --token / --live / --json 放在动作之前;也可用环境变量 USBIPD_HUB / USBIPD_USER / USBIPD_PASSWORD。--json 输出纯 JSON 供脚本消费(提示信息走 stderr)。
主要配置(环境变量,CLI 显式参数优先)
| 变量 | 默认值 | 用途 |
|---|---|---|
USBIPD_SECRET / USBIPD_SECRET_FILE |
首次自动生成并落盘 | HMAC 令牌签名密钥,不硬编码 |
USBIPD_DB |
usbipd.db |
SQLite 单文件(WAL) |
USBIPD_AGENT_TOKEN |
空(不校验) | node-agent 注册令牌,生产必填 |
USBIPD_BOOTSTRAP_PASSWORD |
随机生成并打印一次 | 首次启动管理员口令 |
USBIPD_RELAY_HOST / USBIPD_RELAY_PORT / USBIPD_RELAY_BIND |
127.0.0.1 / 3240 / 127.0.0.1 |
relay 客户端连接地址、监听端口与绑定地址 |
USBIPD_DRY_RUN |
1 |
0 时 agent 才真正执行防火墙与复位命令 |
USBIPD_LEASE_TTL / USBIPD_LEASE_MAX_TTL / USBIPD_LEASE_ABSOLUTE_MAX |
3600 / 28800 / 86400 秒 | 默认租期、单次上限、累计绝对上限 |
USBIPD_FORCE_RELAY / USBIPD_ALLOW_SAME_PRIORITY_PREEMPT / USBIPD_STORAGE_PREEMPTIBLE |
0 |
Q-13 / Q-18 / Q-21 策略开关 |
USBIPD_HEARTBEAT_TIMEOUT |
90 秒 | 节点掉线收敛 |
USBIPD_WEB |
0 |
1 启用 Web 控制台(等价 hub --web) |
实现范围与规格映射
| 状态 | 规格项 |
|---|---|
| 🔵 已实现 | S-01~S-12(注册/采集/刷新/查询/账号/设备/租约/直连准入/客户端三动作/自愈骨架/审计)、S-13 Web 控制台(可选 --web)、S-15~S-18(路径决策、中继转发、协议感知校验、虚拟 busid)、S-20~S-25(三动作、到期强断、优先级、抢占、管理员收回) |
| 🟡 简化实现 | S-11 自愈(客户端需手工重连;无自动重连守护进程)、S-19 中继高可用(单 relay)、S-14 预约排队(未做) |
| ⛔ 未做 | 重写 USB/IP 协议栈(N-07)、计费/审批(N-02)、2×2 平台矩阵实机验证(M0) |
验收映射:A-07/A-18/A-19/A-20/A-21/A-22 由 tests/test_lease.py + selftest.py 覆盖;A-15(DEVLIST 过滤、越权 IMPORT、主动断链)由 tests/test_relay_integration.py 用真实 TCP + 假宿主验证;A-23(2×2 平台矩阵)需 M0 实机验证,代码侧已具备 Linux/Windows 双采集与双策略实现。
生产部署前置(务必阅读)
- 直连准入必须预置默认 DROP(Q-03 方案 A),否则白名单形同虚设:
- Linux:
iptables -A INPUT -p tcp --dport 3240 -j DROP - Windows:
netsh advfirewall firewall add rule name=usbipd-default-block dir=in action=block protocol=TCP localport=3240agent 只增删 ACCEPT 规则;relay 路径下宿主只放行 relay 主机 IP。
- Linux:
- relay 端口与客户端端口必须一致:默认 TCP 3240。Linux
usbip与 Windowsusbip-win2的usbip.exe都支持全局--tcp-port/-t指定端口(ArchWiki《USB/IP》、usbip-win2 CLI 文档;检索 2026-11-05),所以USBIPD_RELAY_PORT改成非 3240 时,客户端map会自动在usbip attach前追加--tcp-port <port>。之所以仍选单中继端口 + 全局virtual_busid+ 源 IP 路由(而非每租约一个端口),是为了少开防火墙端口、兼容不带该选项的旧工具链、简化会话管理;connect_plan.attach_port已保留,未来可切动态端口。跨网段时把--relay-host设为 hub 对外 IP、--relay-bind 0.0.0.0;Windows 上 3240 若被保留,先查netsh interface ipv4 show excludedportrange protocol=tcp。宿主导出服务端口若改成非 3240,用agent --usbip-port <port>注册,connect_plan与白名单增删会同步该端口。 - 必须实现 USB/IP v1.1.1 报文(F-08):Linux 内核宿主与本项目 relay 均满足;否则 Windows 客户端连不上。Windows 客户端优先用
usbip-win2 ≥ v0.9.7.3微软签名驱动(ADR-06),测试签名仅作降级且装后不得关闭 testsigning(F-09)。 - 安全:设置
USBIPD_AGENT_TOKEN、USBIPD_SECRET,控制面前面挂 HTTPS 反向代理(当前为 HTTP + Bearer 令牌),审计表只追加不提供删除接口;.usbipd_client.json/.usbipd_sessions.json含令牌,勿提交版本库。 - 强拆数据风险(Q-21):存储/音视频类设备默认禁止抢占;强制断开前是否已优雅 detach 取决于客户端实现,U 盘等正在读写时仍可能损坏数据。
目录结构
usbipd/
common/ 配置、错误码、PBKDF2/HMAC 鉴权、JSON HTTP 客户端
core/ 模型、SQLite 事务、租约状态机、数据面执行器
hub/ REST 路由、后台到期扫描、服务启动、CLI
hub/static/index.html Web 控制台(可选 --web,单文件原生 JS)
agent/ Linux sysfs / Windows usbipd list 采集、防火墙与复位策略、心跳
client/ 三动作 CLI、usbip attach/detach 封装
relay/ v1.1.1 报文解析、会话注册表、asyncio 长连接中继
selftest.py
tests/ 37 项 unittest(含真实 TCP 中继集成测试、Web UI 启用/禁用)
pyproject.toml / uv.lock / .python-version uv 项目管理(Python 3.12)
目标与规格书.md
已知限制
- hub 为单实例 SQLite,未做多副本/高可用;relay 重启会断开全部活跃 attach(F-07,规格已标注)。
- 数据面连接与租约状态的强一致依赖 node-agent 在线回报;
dry-run下「断链校验」只验证会话已关闭,真机需配合定期对账把残留升级为fault。 - Windows 采集依赖
usbipd list文本输出,只能拿到 busid/VID:PID/STATE;设备类、序列号、速度、driver_bound无法准确判定(如实标unknown,Linux 侧由 sysfs 判定)。因此 Windows 节点新上报的设备默认禁止抢占,管理员确认设备类型后可用PATCH /devices/{id} {"preemptible": true}显式放开;心跳对账不会覆盖该设置。 - 非 3240 宿主端口需用
agent --usbip-port PORT注册,connect_plan与防火墙准入/撤销命令会同步使用该端口。 - Web 控制台默认关闭;启用后与 JSON API 共用同一端口与 Bearer 令牌(token 存 localStorage),控制面仍是 HTTP,生产环境必须在前面挂 HTTPS 反向代理,并只对受控网络开放
--web。 - 尚未做 M0 实机 2×2 矩阵验证,Windows 客户端驱动与设备类兼容性需实测(F-04/F-08);本仓所有自动化验证均在
dry-run下完成,从未发生过真实usbip attach。
Metadata
Release files for usb-device-farm 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| usb_device_farm-0.5.0.tar.gz | 96.2 kB | Without provenance | Details for usb_device_farm-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| usb_device_farm-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for usb_device_farm-0.5.0-py3-none-any.whl |
Total release size: 168.7 kB
Release files / usb_device_farm-0.5.0.tar.gz
| Download URL | usb_device_farm-0.5.0.tar.gz |
|---|---|
| Size | 96.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
50b0d2a94eb131520e7a5b1821c8a5d6eee6fb0fd810b7a796dde46a665b640a
|
|
BLAKE2b-256 checksum How to use checksums |
1ba17d80b21df302da4438c43e365240f3f434626c328dfe4f1dd5a7c982712a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / usb_device_farm-0.5.0-py3-none-any.whl
| Download URL | usb_device_farm-0.5.0-py3-none-any.whl |
|---|---|
| Size | 72.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
20055277ff34e1683e68656da51634b95900a944f345c7a158307bd35a8cfda7
|
|
BLAKE2b-256 checksum How to use checksums |
6d1113dfbdc042b35e6cdc3b0004d730263883d2880b7e419e8fbff85307a132
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for usb-device-farm 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.