diff --git a/README.md b/README.md index bbac36b..9fb21c6 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ | [npm-grpc-fix.md](./npm-grpc-fix.md) | 透過 Nginx Proxy Manager 對外公開時的 gRPC 修復 | | [setup-keys-and-pat.md](./setup-keys-and-pat.md) | Setup Keys 概念、PAT 用法、以及 API 無法改名的繞道做法 | | [stun-port-conflict.md](./stun-port-conflict.md) | STUN 3478 被 Headscale 佔用 → NetBird 改用 3479 的解法 | +| [client-troubleshooting.md](./client-troubleshooting.md) | 客戶端 P2P 失敗、bufferbloat、Force Relay 等坑 | | [SUMMARY.md](./SUMMARY.md) | 部署摘要與疑難排解記錄 | ## 快速開始 diff --git a/SUMMARY.md b/SUMMARY.md index 643bc8f..492caa6 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -83,6 +83,8 @@ reopen fd 8: permission denied | 手機 App:`server closed the stream without sending trailers` | 透過 NPM 對外公開時,NPM 用 `proxy_pass` 而非 `grpc_pass` 轉 gRPC;加上 Caddy `:80` 預設不支援 h2c | NPM 加 `advanced_config`(gRPC 路由)+ Caddy 全域啟用 `protocols h1 h2 h2c`;見 [npm-grpc-fix.md](./npm-grpc-fix.md) | | `PUT /api/setup-keys/{id}` 回 200 但 `name` 沒改 | NetBird API 只更新 `auto_groups` 和 `revoked`,`name` 是 read-only | 改 `store.db` 的 `setup_keys.name` → 重啟 `netbird-server`;見 [setup-keys-and-pat.md](./setup-keys-and-pat.md) | | peer 之間一直走 Relay,`[stun:...:3478] is Checking...` | STUN port 3478 被 Headscale (`.126`) 搶先佔用 DNAT | NetBird 改用 3479/udp(server config + OpenWrt DNAT);見 [stun-port-conflict.md](./stun-port-conflict.md) | +| STUN 都通了,仍然 `Connection type: Relayed`、`ICE candidate: -/-` | 客戶端 **Force Relay Connection** 開關被打開,跳過 ICE gathering | App Settings → Advanced → 關掉 Force Relay;見 [client-troubleshooting.md](./client-troubleshooting.md) | +| ping RTT 尖峰到 2-4 秒 | 當下 WiFi/ISP 有 bufferbloat | 用 `networkQuality -v` 確認(看 RPM),與 NetBird 無關 | ## 檔案位置 @@ -95,7 +97,8 @@ reopen fd 8: permission denied ├── netbird-selfhosted-setup.md ← 完整安裝指南 ├── npm-grpc-fix.md ← 經 NPM 對外公開時的 gRPC 修復 ├── setup-keys-and-pat.md ← Setup Keys / PAT 操作與 API 限制繞道 -└── stun-port-conflict.md ← STUN 改用 3479 (3478 被 Headscale 佔用) +├── stun-port-conflict.md ← STUN 改用 3479 (3478 被 Headscale 佔用) +└── client-troubleshooting.md ← 客戶端 Force Relay / bufferbloat 排錯 ``` ### 遠端伺服器(192.168.42.127:/opt/netbird/) diff --git a/client-troubleshooting.md b/client-troubleshooting.md new file mode 100644 index 0000000..00b1fa4 --- /dev/null +++ b/client-troubleshooting.md @@ -0,0 +1,147 @@ +# NetBird 客戶端 Troubleshooting + +記錄一些不容易猜到的客戶端問題與解法。 + +## P2P 永遠打不通:先檢查 `Force Relay Connection` + +### 症狀 + +`netbird status --detail` 顯示: + +``` +Connection type: Relayed +ICE candidate (Local/Remote): -/- +ICE candidate endpoints (Local/Remote): -/- +Relay server address: rels://... +``` + +兩端 ICE candidate 都是 `-/-`、長時間不變,連線可用但永遠走 Relay。 + +### 容易猜錯的方向 + +- 怪 STUN(DNAT、port 衝突、防火牆) +- 怪「Block Inbound Connections」 +- 怪 iOS 平台限制(NetworkExtension 不支援 P2P UDP) +- 怪同 WiFi 下的 hairpin NAT +- 怪 Mac 防火牆 + +以上都檢查無誤後仍然 `Relayed` → **檢查客戶端的 `Force Relay Connection`**。 + +### 真正原因 + +NetBird 客戶端有一個 **「Force relay connection」** 開關。打開後 NetBird 完全跳過 ICE gathering,直接走 Relay。 + +#### 各平台位置 + +| 平台 | 路徑 | +|------|------| +| iOS | App → Settings → Advanced → 最下方「Force relay connection」 | +| macOS | 選單列 NetBird → Settings → Advanced Settings(或 menu 內找 Force Relay) | +| CLI / daemon | `netbird up` 沒有對應 flag;以 daemon 設定檔方式存在 | + +#### 修復 + +1. 把開關關掉 +2. App 內 Disconnect → Connect(或 `netbird down && netbird up`) +3. 等 5-10 秒讓 ICE gathering 完成 + +### 驗證 P2P 成功 + +`netbird status --detail` 應顯示: + +``` +Connection type: P2P +ICE candidate (Local/Remote): host/prflx ← 或 srflx/srflx 等組合 +ICE candidate endpoints (Local/Remote): 192.168.x.x:51820/192.168.x.x:51820 +Latency: ~25 ms ← 同 LAN 應該 <50ms +``` + +`Connection type` 從 `Relayed` 變成 `P2P` 就成功。 + +## 高 RTT 大幅波動:先排除 bufferbloat + +### 症狀 + +`ping ` 結果像這樣: + +``` +round-trip min/avg/max/stddev = 220.423/2230.078/4238.380/1420.726 ms +``` + +平均 2 秒、尖峰 4 秒。看起來像 NetBird 出問題。 + +### 診斷 + +在 macOS 跑: + +```bash +networkQuality -v +``` + +如果輸出: + +``` +Responsiveness: Low (703 milliseconds | 85 RPM) +``` + +→ **這是你連的 WiFi/ISP 有 bufferbloat**,跟 NetBird 無關。 +RPM (Round-trips Per Minute) 應該 >500,<100 算很糟。Bufferbloat 在路由器/modem 有大量佇列堆積時會讓任何流量延遲爆衝。 + +### 修復方向 + +- 換更好的 WiFi AP(支援 SQM / fq_codel / cake) +- ISP modem 啟用 SQM(OpenWrt 上裝 `luci-app-sqm`) +- 短期:避免在背景下載大檔時測 NetBird 延遲 + +## 客戶端配置驗證 checklist + +當你覺得「明明設定都對,但 NetBird 行為怪怪的」,依序檢查: + +```bash +# 1. 客戶端有連到正確的 management server? +netbird status | grep Management + +# 2. STUN 是 Available?(這個案例的 STUN port 是 3479) +netbird status --detail | grep -E 'stun:|Relays:' + +# 3. 看到對方 peer? +netbird status --detail | grep -A 1 + +# 4. Connection type 是 P2P 還是 Relayed? +netbird status --detail | grep 'Connection type' + +# 5. ICE candidate 是什麼類型?(host/srflx/prflx/relay) +netbird status --detail | grep 'ICE candidate' + +# 6. WireGuard 真的有 handshake? +netbird status --detail | grep 'Last WireGuard handshake' + +# 7. 真的能 ping? +ping -c 5 +``` + +如果 7 通但 4 是 `Relayed` 且 5 是 `-/-` → 99% 是 Force Relay 開著(見上)。 + +## 各平台 NetBird Settings 裡會踩雷的開關 + +| 開關 | 開啟後果 | +|------|---------| +| **Force relay connection** | 永遠走 Relay,P2P 失效 | +| Block Inbound Connections | 對方主動連入會被擋,可能逼成單向 Relay | +| Disable Client Routes | 此 peer 不路由到其他 peer | +| Disable Server Routes | 此 peer 無法當別人的 router | +| Disable LAN Access | 當 exit node 時不能存取 exit node 的本地網路 | +| Lazy Connections | 連線延遲建立直到有流量才協商 | +| Enable Rosenpass | 量子抗性握手(實驗性,可能與舊 peer 不相容) | + +預設值通常是「全部關閉」 — 如果你或別人不小心開了某個,行為就會變得難解釋。 + +## 為什麼客戶端版的 docs 不直接寫死「永遠關 Force Relay」 + +某些情境下強制 Relay 是合理的: + +- P2P 隱私顧慮(不想暴露真實 IP 給對方 peer) +- 對方所在網路有 IDS 會偵測 P2P UDP 流量 +- Debug 時希望流量都過 Relay 方便抓 log + +所以 NetBird 把它做成可選。**自宅 mesh / 個人裝置之間互通,沒理由開**。