USB Passthrough — 操作員指南
實際把 host 機器上的 USB 裝置借給遠端 viewer 用的步驟手冊。host 端在
Linux libusb 上端到端運作;Windows WinUSB 與 macOS IOKit 已實作但
硬體未驗證——兩者都必須先通過 Phase 2e 硬體測試矩陣才能用於
production。default_passthrough_backend() 會依當前 OS 自動挑 backend。
如果你是安全審查者而非操作員,請看 USB Passthrough — Phase 2e 安全審查清單。如果你想要協定細節, 請看 USB Passthrough — 第二階段設計。
前置需求
在 host(有實體 USB 裝置的機器)上:
Python 3.10+ 並安裝 AutoControl。
選用的
webrtc套件:pip install je_auto_control[webrtc]。如要使用 libusb backend 需安裝
pyusb:pip install pyusb。預計給 viewer 用的 USB 裝置已插上。
各 OS 設定(見下方 driver 設定)。
在 viewer(將使用該裝置的遠端機器)上:
Python 3.10+ 並安裝 AutoControl。
能連到 host 的 REST API port(預設 9939),且 在 NAT 後方時 能連到 WebRTC signalling / TURN 端點。
host 的 bearer token(操作員以帶外管道交付)。
Driver 設定(依 OS)
Linux(libusb)
libusb backend 是目前最完整測試過的路徑。步驟:
安裝
libusb-1.0開發檔(例如apt install libusb-1.0-0)。加上
udevrule,讓 AutoControl host 程序不需要 root 就能 claim 裝置。例:YubiKey 5(vendor1050、product0407):# /etc/udev/rules.d/99-autocontrol-usb.rules SUBSYSTEM=="usb", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0407", MODE="0660", GROUP="plugdev"
接著
sudo udevadm control --reload && sudo udevadm trigger。確認 AutoControl 使用者在
plugdev群組。若裝置是 HID,AutoControl 的 libusb wrapper 會在
open時 detachusbhid,close時 re-attach。所以在 claim HID 裝置時 你的本機鍵盤輸入可能會短暫停頓,這是正常。
Windows(WinUSB)— 硬體未驗證
ctypes 接線已寫但尚未對實體硬體驗證。視為 alpha。步驟:
用 Zadig 或 libwdi 把目標裝置綁到 WinUSB driver。不要 對 host OS 已經管理的裝置做這件事 (印表機、hub、鍵盤)。
綁好後裝置應該會出現在
WinusbBackend().list()中。在依賴 transfer 之前需要硬體測試。期待的測試矩陣見安全審查清單。
macOS(IOKit)— 硬體未驗證
IokitBackend 透過原生 IOKit(ctypes,不需 pyobjc)列舉 USB
裝置,所以 IokitBackend().list() 可用。claim 裝置做 transfer 則
委派給 libusb,請安裝:pip install pyusb 與 brew install libusb。
注意:
直接散布(非 App Store)的 build 必須 notarisation;libusb 存取 裝置不需特殊 entitlement。
System Integrity Protection 會藏起 Apple 內部裝置與某些 USB-C 週邊——它們不會出現在
list()也無法 claim,屬正常。transfer 為硬體未驗證;依賴前請看安全審查清單的測試矩陣。
啟用 feature
USB passthrough 預設 off。兩種開啟方式:
環境變數,於程序啟動時讀取:
export JE_AUTOCONTROL_USB_PASSTHROUGH=1 python -m je_auto_control.cli start-rest
程式控(覆蓋環境變數),於你的 bootstrap 腳本中:
from je_auto_control.utils.usb.passthrough import enable_usb_passthrough enable_usb_passthrough(True)
確認用 is_usb_passthrough_enabled():
from je_auto_control.utils.usb.passthrough import is_usb_passthrough_enabled
assert is_usb_passthrough_enabled()
ACL 設定
ACL 預設為 "deny",所以 viewer 無法 claim 操作員未核准的裝置。
新增 per-device rule:
從 GUI — host 的 USB 分頁在第一次 OPEN 未知裝置時會跳出 prompt 對話框。勾 記住這個決定 把永久 allow rule 寫入。
從 Python:
from je_auto_control.utils.usb.passthrough import ( AclRule, UsbAcl, ) acl = UsbAcl() acl.add_rule(AclRule( vendor_id="1050", product_id="0407", serial=None, # match 任何 serial label="YubiKey 5", allow=True, prompt_on_open=False, # 一旦核准就靜默 allow ))
直接編輯
~/.je_auto_control/usb_acl.json。檔案有權限檢查 (POSIX 上 mode0600)。壞 JSON 或未知version會退到 預設 deny。若你手動編輯檔案,HMAC 簽章會對不上而導致 ACL fail-closed(見下)——請改用UsbAcl重新儲存以刷新簽章。
決策優先序:
第一個 match 的 rule 勝。
prompt_on_open=True表示每次都重問 操作員,即使 rule 是allow=True。沒有 rule match 時套用檔案的
default(預設"deny")。
ACL 檔案完整性(HMAC)
ACL 旁附一個 usb_acl.json.sig sidecar HMAC-SHA256 簽章。載入時對
檔案位元組驗證;不符就 fail-closed(default-deny,
UsbAcl.integrity_ok 回 False)。這擋住偷偷改寫 JSON 想給自己
授權的 process。
預設簽章金鑰是隨機 32-byte 檔
usb_acl.json.key(POSIX 上 mode0600),首次儲存時建立。高保證情境請從平台 keychain 衍生金鑰並明確傳入:
UsbAcl(hmac_key=<bytes>)。否則同使用者身分的 process 可讀金鑰檔 而偽造簽章。傳
UsbAcl(require_signature=True)可連 legacy 未簽章檔也一併拒絕。
啟動 host
host 需要 REST API 在跑(這樣 viewer 才能列舉),加上一條對 viewer 的 WebRTC peer connection(這樣 transfer 才能流動)。
REST:
from je_auto_control.utils.rest_api import start_rest_api_server
server = start_rest_api_server(host="0.0.0.0", port=9939)
print("Bearer:", server.token)
WebRTC:用既有的遠端桌面流程(見 維運與管理層)建立
session。viewer 端的 UsbPassthroughClient 之後就接到談妥的
DataChannel 上。
Viewer 端:claim 與 transfer
列舉
從 Python:
import urllib.request, json
req = urllib.request.Request(
"http://host:9939/usb/devices",
headers={"Authorization": f"Bearer {token}"},
)
with urllib.request.urlopen(req) as r:
body = json.loads(r.read())
for d in body["devices"]:
print(d["vendor_id"], d["product_id"], d.get("product"))
或用 viewer 端的 USB Browser GUI 分頁:貼上 host 的 REST URL + token,按 Fetch devices。
Open + transfer
from je_auto_control.utils.usb.passthrough import (
UsbPassthroughClient, encode_frame, decode_frame,
)
# `data_channel` 是你 WebRTC 上 "usb" channel 的 RTCDataChannel。
def send(frame):
data_channel.send(encode_frame(frame))
client = UsbPassthroughClient(send_frame=send)
# 接上 channel 的 on-message callback:
data_channel.on("message")(lambda raw: client.feed_frame(decode_frame(raw)))
handle = client.open(vendor_id="1050", product_id="0407")
response = handle.control_transfer(
bm_request_type=0xC0, b_request=6, w_value=0x0100, length=18,
)
print("device descriptor:", response.hex())
handle.close()
client.shutdown()
錯誤:
UsbClientTimeout— host 超過reply_timeout_s(預設 10 秒) 沒回。檢查網路 / host 程序。UsbClientError— host 回{ok: false, error: ...}。最常見 情境是 denied by ACL policy — 去看 host 端的 prompt 對話框或 ACL 規則。UsbClientClosed— client 或其 handle 已 shutdown。
疑難排解對照表
症狀 |
可能原因/處理 |
|---|---|
|
沒有 allow rule 且 |
|
裝置沒被列舉。看 |
transfer 上 |
viewer 送的 frame 超過 host |
Transfer |
host 程序忙或 WebRTC channel 壞了。看 Packet Inspector 分頁的 RTT / 封包遺失。 |
OPEN 後 host 鍵盤停止運作 |
Linux:HID 裝置被 claim 且 |
稽核鏈顯示 |
有人直接編輯了 |
無 GUI 控制(AC_usb_* 指令)
GUI 能做的事都有對應的 executor 指令,所以 JSON action 檔、socket server 與排程器都能在沒有 GUI 的情況下驅動 USB passthrough:
JSON action 範例:
[
["AC_usb_passthrough_enable", {"enabled": true}],
["AC_usb_acl_add", {"vendor_id": "1050", "product_id": "0407"}],
["AC_usb_loopback_open", {"vendor_id": "1050", "product_id": "0407"}]
]
同樣的操作另外提供兩個介面:
REST API —
GET/POST /usb/passthrough/...、/usb/acl...、/usb/loopback/...、/usb/remote/...(需 bearer token;見/openapi.json)。ACL 匯入/匯出刻意 不 開 REST(伺服器端 檔案路徑風險)。MCP — 一級
ac_usb_*工具(ac_usb_loopback_open…),帶 JSON Schema,agent 可直接呼叫。
尚未發布的部分
USB 分享 分頁是簡易的 AnyDesk 風介面:左側啟用分享並對本機裝置 做 ACL 允許/封鎖;右側經 in-process channel 列出分享裝置並 開啟 其中一個(讀描述元即證明整條堆疊運作)。USB Browser 分頁的 Open 按鈕現在對 localhost 目標也會走同一條 loopback 路徑。
跨機器已完整串接:WebRTC host 建立
usbDataChannel,viewer 以viewer.usb_client()暴露UsbChannelClient(含list_devices/open/resume)。USB 分享 面板有 來源 下拉:選 遠端(WebRTC) 時 List / Open 會對 live WebRTC viewer 的主機操作 (經registry.webrtc_usb_client());選 本機(loopback) 則走同機。 亦可從 Python 驅動。Windows WinUSB 與 macOS IOKit 的 transfer 路徑已寫但尚未對實體硬體 驗證。在 Phase 2e 硬體測試矩陣通過前請勿用於 production。
Phase 2e 外部安全審查尚未簽核;feature flag 必須維持顯式 opt-in。