HexVM 工程實務

雲端 Mac 弱網重現:可回復的鏈路整形實作

雲端 Mac 弱網重現:可回復的鏈路整形實作

遠端除錯 iOS 用戶端時,最難重現的通常不是完全斷線,而是「仍可連線,但回應非常緩慢」:交握延遲增加、上傳速度下降、少量請求遺失,最終引發重複提交、無限重試,或讓介面長時間停留在等待狀態。在 HexVM 雲端 Mac 上進行這類測試時,首要原則不是立即限速,而是先將應用程式的測試流量與 SSH、VNC 等管理流量分開。否則,一條範圍過大的規則就可能切斷自己的操作入口。

先定義可驗收的弱網情境

不要以「網路很差」作為測試條件。這種描述既無法重現,也無從判斷修正是否有效。應先將各種情境寫成固定參數,並為每個情境定義用戶端的預期行為。

情境 下載與上傳限制 額外延遲 封包遺失率 主要驗收項目
基準 不限制 0 ms 0 正常請求耗時與成功率
高延遲 8 Mbit/s 120 ms 0 載入狀態、取消與逾時
低頻寬 1 Mbit/s 80 ms 0 上傳進度與切換至背景
不穩定鏈路 4 Mbit/s 100 ms 2% 重試上限與冪等處理

這些參數是測試輸入,並非對節點網路品質的描述。團隊應依照 API 逾時設定、檔案大小及使用者所處的網路環境調整數值,但同一輪迴歸測試必須維持一致的參數。

弱網測試的目的不是證明請求最終可以成功,而是確認失敗發生時,用戶端能及時結束等待、顯示易於理解的狀態,而且不會產生重複資料。

建立未限速的基準

載入任何流量整形規則之前,先記錄 DNS、路由及介面回應。測試介面應由團隊自行控制,並提供不會寫入正式環境資料的探測路徑。

export TEST_URL="https://test-endpoint.invalid/health"
scutil --dns | grep 'nameserver\[[0-9]*\]'
route -n get default
networkQuality
curl -sS -o /dev/null \
  -w 'code=%{http_code} connect=%{time_connect} tls=%{time_appconnect} total=%{time_total}
' \
  "$TEST_URL"

範例網域名稱必須替換成實際受控的測試位址。至少記錄三次請求的中位數,不要只保留表現最佳的一次。若在基準階段已經出現 DNS 失敗、憑證錯誤或路由異常,應先解決基礎問題,不能藉由增加重試次數加以掩蓋。

還需確認目標位址是否由多個 IP 提供服務。若只限制其中一個 IP,請求可能會解析至其他位址,導致同一項測試時快時慢。較穩妥的做法是為弱網驗收準備固定入口,並在執行前儲存 DNS 解析結果。

僅整形目標介面的流量

macOS 的 dnctl 可建立頻寬限制與延遲管道,pf 則負責將符合條件的流量導入管道。規則必須同時限定目標 IP、通訊協定與連接埠,不可使用涵蓋所有對外連線的萬用條件。

以下指令碼會建立一條 8 Mbit/s、延遲 120 ms、封包遺失率 2% 的測試管道。TARGET_IP 應填入受控介面的固定位址。

set -euo pipefail

TARGET_IP="${TARGET_IP:?set TARGET_IP first}"
PIPE_ID=310
ANCHOR="com.apple/hexvm-nettest"

cleanup() {
  sudo pfctl -a "$ANCHOR" -F all >/dev/null 2>&1 || true
  sudo dnctl delete "$PIPE_ID" >/dev/null 2>&1 || true
}

trap cleanup EXIT INT TERM

sudo dnctl pipe "$PIPE_ID" config bw 8Mbit/s delay 120 plr 0.02
printf 'dummynet out quick proto tcp from any to %s port 443 pipe %s
' \
  "$TARGET_IP" "$PIPE_ID" |
  sudo pfctl -a "$ANCHOR" -f -

sudo pfctl -E >/dev/null
sudo pfctl -a "$ANCHOR" -sr -v

curl -sS -o /dev/null \
  -w 'code=%{http_code} connect=%{time_connect} total=%{time_total}
' \
  "$TEST_URL"

先透過唯讀的健康檢查確認規則已命中,再執行會寫入資料的業務測試。不要將 pfctl -d 當成清理手段,因為機器上可能還有其他防火牆規則。指令碼只會清空自己的 anchor,並刪除自己的 pipe,以免影響並行執行的工作。

避免誤傷遠端工作階段

執行前應保留第二個管理終端,並確認規則未涵蓋管理連接埠、預設閘道或任意目標萬用字元。若測試目標與管理連線共用同一個 IP,應改用獨立的測試入口,不要仰賴規則順序碰運氣。

將用戶端行為轉換成自動化斷言

弱網驗收不能只確認請求「是否有回應」。測試程式碼應記錄嘗試次數、最終錯誤類型、總等待時間及請求識別碼。對於寫入操作,伺服器端與用戶端都應使用穩定的冪等鍵;發生逾時後,用戶端應先查詢處理結果,再決定是否重試。

可將網路層替換成可觀測的封裝,並檢查以下邊界條件:

  1. 單次連線超過門檻後可取消,不會無限等待。
  2. 重試次數設有上限,間隔包含退避機制,不會形成請求風暴。
  3. 使用者主動取消後,背景工作不會暗中重新傳送。
  4. 上傳中斷時保留明確狀態,不會將失敗顯示成完成。
  5. 網路恢復後可繼續操作,但不會重複建立記錄。

模擬器測試可透過明確的裝置識別碼執行,避免依賴機器上碰巧存在的裝置名稱。

export SIMULATOR_UDID="replace-with-booted-simulator-udid"

xcodebuild test \
  -scheme NetworkBehaviorTests \
  -destination "platform=iOS Simulator,id=${SIMULATOR_UDID}" \
  -resultBundlePath build/NetworkBehavior.xcresult

每個網路情境都應分別儲存結果套件,並將情境參數寫入測試日誌。如此一來,失敗記錄才能回答「在何種延遲與封包遺失條件下失敗」,而非只留下一個無法重現的紅色狀態。

清理規則並完成迴歸測試

指令碼正常結束或收到中斷訊號時會執行清理,但若遠端工作階段異常中斷,仍需手動核對。重新連線後執行:

sudo pfctl -a com.apple/hexvm-nettest -sr
sudo dnctl list
curl -sS -o /dev/null \
  -w 'code=%{http_code} connect=%{time_connect} total=%{time_total}
' \
  "$TEST_URL"

專用 anchor 應為空,清單中也不應再出現編號為 310 的 pipe。接著重新執行基準請求,確認連線時間與總耗時已恢復至測試前的正常範圍。

最後再進行一次未套用整形規則的迴歸測試,重點檢查弱網修正是否改變正常網路環境下的請求順序、快取命中情況及介面回應。將情境參數、用戶端版本、目標介面版本、結果套件路徑與清理結果收錄在同一份執行記錄中。如此一來,弱網測試才能從一次性的手動展示,轉化為可稽核、可重現且能安全退出的工程流程。

常見問題

為什麼不能直接限制雲端 Mac 的全部對外流量?

全域規則會同時影響 SSH、VNC、相依套件下載與應用程式測試,可能造成管理連線中斷。規則應只比對測試目標的 IP、協定與連接埠。

弱網測試後如何確認暫時規則已經清除?

先清空專用 pf anchor,再刪除對應的 dnctl pipe,確認 anchor 內沒有剩餘規則,最後重新執行未限速的基準請求。

弱網環境至少需要驗證哪些用戶端行為?

至少驗證連線逾時、有限次數重試、請求取消、離線提示、重複送出防護,以及網路恢復後不產生重複資料的正常續行。

獨享 Apple Silicon

為下一次建置選擇獨享雲端 Mac

依工作負載選擇 Mac mini 機型、租期與區域。每筆訂單都對應獨立實體節點,實際可用性以控制台即時回傳結果為準。

選擇雲端 Mac 方案