remote.文件首頁

技術說明

這份文件描述當前程式碼裡的幾條核心機制。訊息欄位、幀格式和握手時序的完整表見 線路協議。

運維端和裝置都向外連線 exchange

家用和企業分支的裝置通常在 NAT 後面,exchange 無法主動撥進去。因此兩邊都向 exchange 發起 WebSocket:

  • 裝置連 wss://<主机>/ws/device(明文除錯時是 ws://),在首幀裡帶上裝置令牌註冊
  • 運維端連 wss://<主机>/ws/operator,握手使用運維令牌

exchange 自己只做鑑權、路由和留痕。監聽地址由 --listen 或 REMOTE_LISTEN 決定,TLS 由前面的反向代理終止。

兩套會話編號

運維端和裝置各自維護會話編號,兩邊的數字互不佔用。運維端發出請求時就可以帶上自己的編號,不必先等伺服器端分配再發後續幀。裝置側編號由這臺裝置當前這條連線單獨分配,從 1 起遞增,兩個運維端即使都使用本機編號 1,裝置上也會落到不同的編號。

編號 0 不分配給任何會話,留給連線級訊息。運維側編號為 0,或這條運維連線已經佔用了該編號時,登記直接失敗,不會寫入路由表。

翻譯表在 crates/exchange/src/hub/session_table.rs。hub.rs 把翻譯結果掛到具體連線上:裝置側是「裝置編號 → 運維編號」,運維側是「運維編號 →(裝置連線, 裝置編號)」。

轉發時只改寫會話編號

PTY 位元組和命令的 stdout/stderr 走二進位幀。幀頭 9 位元組:1 位元組流型別、4 位元組 session_id、4 位元組載荷長度,後面是裸載荷。定義在 crates/proto/src/frame.rs。

exchange 轉發這種幀時只替換 session_id:

  • 運維端發往裝置:crates/exchange/src/ws/operator.rs 用 DataFrame::with_session_id 換成裝置側編號,再把編碼後的位元組放進裝置的傳送佇列
  • 裝置發往運維端:crates/exchange/src/ws/device.rs 的 handle_data 把裝置側編號換回運維端自己的編號。原始碼註釋寫的就是這一處改寫

載荷原樣透過。路由不根據 PTY 按鍵或命令輸出的內容做判斷;查不到會話時幀被丟棄。控制訊息是 JSON 文字幀,開啟和結束會話時同樣換成對端那一套編號,業務欄位保持原樣。

裝置重連會頂替舊連線

同一裝置再次連上時,新連線寫入線上表並頂替舊連線,舊連線交給呼叫方關閉。不能靠拒絕新連線來處理「重複連線」:半開的舊連線可能還掛在表裡,拒絕會讓裝置一直等到舊連線逾時。

每條裝置連線有一個 epoch(UUID),在 Hub::register_device 裡生成。斷開清理走 Hub::unregister_device,只有表裡當前記錄的 epoch 與這次清理帶來的 epoch 一致時才摘掉裝置並標為離線。舊連線的清理若晚於新連線註冊,epoch 對不上,函式回傳空列表,新連線繼續留在表裡。

傳送佇列有界,寫滿就斷開這條連線

每條 WebSocket 對應一條容量 512 的佇列,實現在 crates/exchange/src/hub/outbound.rs 的 channel。路由路徑上持有連線表的鎖,所以投遞只用 Sender::try_send,不在鎖裡等待對端。

佇列已滿時 try_send 回傳 SendError::Lagging,並置位關閉訊號。Close 控制訊息不進佇列,只置位同一訊號,因此佇列堵死時仍然能要求連線退出。裝置側與運維側的接入迴圈都在 tokio::select! 裡等待 sender.closed()(crates/exchange/src/ws/device.rs、crates/exchange/src/ws/operator.rs)。訊號一到,轉發迴圈結束,隨後按 epoch 登出這臺裝置或清理這個運維端的會話。對端已經把接收端丟掉時回傳 SendError::Closed,與「來不及消費」區分開。

令牌是字首加密文,密文只存 argon2

明文形如 <prefix>.<secret>。crates/exchange/src/auth.rs 的 split_token 按第一個 . 拆開,空字首或空密文都視為格式錯誤。

簽發時字首是 8 位元組隨機數的十六進位,密文是 32 位元組隨機數的 base64url。資料庫只儲存兩列:

  • token_prefix:明文,帶唯一索引,校驗時按它做一次查詢
  • token_hash:密文的 argon2id PHC 字串,由 hash_secret 生成

裝置和運維帳號都按這個格式存。校驗時用字首取出雜湊,再用 verify_secret 比對密文。明文只在簽發當時回傳給呼叫方,庫裡還原不出來。

argon2 參數是 8 MiB 記憶體、2 次迭代、並行度 1,低於庫的預設檔位。密文是 256 位隨機值,慢雜湊擋不住額外的窮舉,卻會把裝置同時重連變成握手延遲。這組參數只適用於這種隨機令牌。

相關文件