Files
netbird-selfhosted/network-routes.md
Timmy f091499ff3 docs: add NetBird exit nodes via CT100 (TW) and virmach-lax (US)
Covers 0.0.0.0/0 route creation, opt-in client-side selection (different
from subnet routes which auto-apply), and the extra trust exposure when
routing through a third-party VPS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 23:39:36 +08:00

308 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Network RoutesSubnet Router
讓 NetBird peers 透過某個「帶路人」存取 LAN 上**沒裝 NetBird 的**裝置印表機、NAS、監視器、OpenWrt 介面等)。
## 概念
NetBird 的 peer 彼此在 `100.64.0.0/10` 的 overlay 網路互通。LAN 裡的印表機、NAS 沒 NetBird 客戶端overlay 網路看不到它們。
**Subnet Router** 的作法:
1. 挑一個在家裡的 peer 當「帶路人」(Routing Peer)
2. 在 NetBird 後台設定「`192.168.42.0/24` 這段要透過這個 peer 走」
3. 在帶路人上開啟 IP forwarding + masquerade
4. 遠端 peer 的路由表拿到 `192.168.42.0/24 → <Routing Peer overlay IP>`LAN 存取自動轉送到帶路人,由帶路人 NAT 進 LAN
```
Mac (外面咖啡店)
│ NetBird tunnel
CT100 (家裡 192.168.42.100, 帶路人)
│ Masquerade (src NAT → 192.168.42.100)
192.168.42.10 ← OpenWrt
192.168.42.40 ← Proxmox
192.168.42.X ← NAS / 印表機 / 任何 LAN 裝置
```
## 本部署的 Route
| 項目 | 值 |
|------|-----|
| Network ID | `home-lan` |
| CIDR | `192.168.42.0/24` |
| Routing Peer | `CT100` (`ct100.netbird.selfhosted`, LAN IP `192.168.42.100`) |
| Distribution Groups | `personal` — Mac、iPhone 的路由表會拿到這條 route |
| Access Control Groups | `personal`, `servers` |
| Masquerade | 開 |
| Metric | 9999低優先度不影響直連 peer 的流量) |
### 為何選 CT100 而不是 CT124
- **CT100** 的 Tailscale 跑 userspace 模式,沒 iptables-legacy 規則,不會跟 NetBird 的 route iptables 打架
- **CT124** 的 Tailscale 是 kernel-mode有個既有的 `ts-input / ts-forward` DROP 100.64.0.0/10 非 tailscale0 規則(見 `peer-deployment-ops.md`)— 當 routing peer 的話 masquerade/forwarding 鏈更容易出狀況
CT100 的 SSH Jump Box 角色也更符合「gateway 型」peer 的語意。
## 部署步驟
### 1. Routing Peer 上開啟 IP forwarding
```bash
ssh root@192.168.42.100
# 即時
sysctl -w net.ipv4.ip_forward=1
# 持久化
cat > /etc/sysctl.d/99-netbird-route.conf << 'EOF'
net.ipv4.ip_forward=1
EOF
sysctl -p /etc/sysctl.d/99-netbird-route.conf
```
NetBird client 容器本身會 handle masquerade 相關 iptables 規則(前提:容器有 `NET_ADMIN` cap`masquerade: true` 設在 route 上)。
### 2. 建 Network RouteAPI
```bash
TOKEN=<你的 PAT>
CT100_PEER_ID="<從 GET /api/peers 查>"
PERSONAL_GID="<personal group id>"
SERVERS_GID="<servers group id>"
curl -X POST -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d "{
\"description\": \"Home LAN via CT100 jump box\",
\"network_id\": \"home-lan\",
\"network\": \"192.168.42.0/24\",
\"peer\": \"$CT100_PEER_ID\",
\"masquerade\": true,
\"metric\": 9999,
\"enabled\": true,
\"groups\": [\"$PERSONAL_GID\"],
\"access_control_groups\": [\"$PERSONAL_GID\", \"$SERVERS_GID\"]
}" \
https://netbird.timmy.us.kg/api/routes | jq
```
欄位說明:
- `peer` — 帶路人 peer ID單一 peer若要 HA 可用 `peer_groups`
- `groups`**路由分發群組**:這些群組的 peers 會在路由表拿到 `192.168.42.0/24 → 100.x.x.x(routing peer)`
- `access_control_groups`**存取控管群組**:誰能使用這條 route
- `masquerade` — SNAT 給回應封包找得到路
- `metric` — 優先度,值越大越低優先;直連 peer 的路由 metric 小,不會被 subnet route 蓋掉
### 3. UI 操作(替代方式)
Dashboard → **Network Routes****Add Route**
- Network CIDR: `192.168.42.0/24`
- Network Identifier: `home-lan`
- Routing Peer: 選 CT100
- Distribution Groups: 選 `personal`
- Access Control Groups: 選 `personal`, `servers`
- Masquerade: **on**
- Metric: `9999`
## 驗證
### 從遠端 peer 確認路由表
macOS:
```bash
netstat -rn | grep 192.168.42
# 192.168.42 link#39 UCS utun20 ← NetBird 介面
```
Linux:
```bash
ip route | grep 192.168.42
# 192.168.42.0/24 dev wt0 scope link
```
### 測試 ping LAN 裝置
```bash
ping 192.168.42.10 # OpenWrt
ping 192.168.42.40 # pve-03
```
延遲應該相當於「到帶路人的 NetBird 延遲 + 帶路人到 LAN 設備的延遲」,本部署約 20ms。
## 排錯
### 症狀route 建好、routing peer 運作正常,但舊客戶端連不到 LAN
**典型現象**
- `netbird status --detail` 顯示 routing peer 有 `Networks: 192.168.42.0/24`
- 但 client 本機 `netstat -rn | grep 192.168.42` 沒有這條 route
- 或「有些 peer 可以連、有些不行」
**原因**NetBird client 在 daemon 啟動時才建立完整 network map之後 route 政策變更會 push 到 client但**既有的連線狀態若卡住,沒辦法套用新 route**。
**修復(各平台)**
- **macOS**:
```bash
sudo launchctl kickstart -k system/netbird
```
- **iOS**: App 內 Disconnect → 等 5 秒 → Connect
- **Linux systemd**: `sudo systemctl restart netbird`
- **Docker 容器**: `docker compose -f /opt/netbird/docker-compose.yml restart`
**本部署的實例2026-04-18**subnet route 剛建好時測試通過,但 daemon 一直沒重啟的裝置iPhone、CT124後來出現 `Status: Connecting` 假象route 也沒進 routing table。全裝置 kickstart / reconnect 後恢復。
### iOS RTSP / 特定 app 不走 NetBird route
iPhone 上**某些 app** 不吃系統 VPN 路由(特別是直接用 socket API 的 RTSP 播放器),即使 NetBird route 設好也連不到。
**排查步驟:**
1. 在 iPhone Safari 打開 `http://<目標 LAN IP>/`
2. 如果能通 → NetBird route 正常,問題在那個 app 對 VPN 的支援
3. 如果不能通 → iPhone NetBird daemon 要重連(見上一節)
**常見解法**
- 換一個支援 VPN 的 RTSP appVLC 通常 OK
- 或讓 app 用 FQDN 而非 IP例如 `rtsp://192-168-42-150.netbird.selfhosted:554/stream1` 如果 NetBird DNS 有設)
### 路由表拿到但 ping 不通
通常是**帶路人的 IP forwarding 沒開**。再檢查:
```bash
ssh <routing peer>
sysctl net.ipv4.ip_forward
# 應為 1
```
### Masquerade 相關
NetBird client 會自動設定 iptables MASQUERADE 規則。如果 LAN 設備看到源 IP 是 `100.x.x.x`NetBird overlay而不是 `192.168.42.100`,表示 masquerade 沒生效 — 檢查 route 設定中 `masquerade: true`,且容器有 `NET_ADMIN` cap。
### 帶路人是 LXC 容器時
有些 LXC CT非特權、沒 `NET_ADMIN`)不能改 iptables / MASQUERADE。確認 CT config 有:
```
features: nesting=1
lxc.cgroup.devices.allow: a # 或其他合適設定
```
本部署的 CT100 就是這樣配置,沒問題。
### 遇到 Tailscale 干擾
如果帶路人同時跑 Tailscale kernel-mode`iptables-legacy` 的 `ts-forward` 會擋掉往 tailscale0 外的 forward。見 `peer-deployment-ops.md` 的 Tailscale 衝突章節。
**本部署避開這問題的方式:選 CT100Tailscale userspace當 routing peer**,不選 CT124。
## Exit Nodes`0.0.0.0/0` 路由)
Exit node 是 subnet router 的特例CIDR 改成 `0.0.0.0/0`,讓選擇此路由的 peer 把**所有**網際網路流量經由該 routing peer 出去(等同 Tailscale 的 Exit Node、WireGuard 的 `AllowedIPs = 0.0.0.0/0`)。
### 本部署的 Exit Nodes
| Network ID | Routing Peer | 出口 IP | 用途 |
|-----------|-------------|--------|------|
| `exit-ct100` | CT100 (`ct100.netbird.selfhosted`) | HiNet `125.229.110.50` (TW) | 家用、回家連 LAN 順便 |
| `exit-virmach-lax` | virmach-lax (`virmach-lax.netbird.selfhosted`) | VirMach `141.11.93.252` (US LAX) | 美國 IP、bypass 地理限制 |
兩條設定一致(只差 `peer`
- CIDR: `0.0.0.0/0`
- Distribution Groups: `personal` — 只推給 Mac/iPhone。**不推給 `servers`**,避免 CT101 拿到 `0.0.0.0/0` 繞回自己或另一個 server
- Access Control Groups: `personal`, `servers`
- Masquerade: on
- Metric: 9999與 home-lan 一致,僅限兜底)
### 建立方式API
跟 home-lan 流程一樣(需 routing peer 先開 IP forwarding只是 `network` 改 `0.0.0.0/0`、`network_id` 改成識別用名稱:
```bash
TOKEN=<你的 PAT>
CT100_PEER_ID="<從 GET /api/peers 查>"
VIRMACH_PEER_ID="<從 GET /api/peers 查>"
PERSONAL_GID="<personal group id>"
SERVERS_GID="<servers group id>"
curl -X POST -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d "{
\"description\": \"Exit node via CT100 (home, TW IP)\",
\"network_id\": \"exit-ct100\",
\"network\": \"0.0.0.0/0\",
\"peer\": \"$CT100_PEER_ID\",
\"masquerade\": true,
\"metric\": 9999,
\"enabled\": true,
\"groups\": [\"$PERSONAL_GID\"],
\"access_control_groups\": [\"$PERSONAL_GID\", \"$SERVERS_GID\"]
}" \
https://netbird.timmy.us.kg/api/routes | jq
# 同樣的 payload 把 network_id 換成 exit-virmach-lax、peer 換成 VIRMACH_PEER_ID 再打一次
```
### Client 端exit node 是 opt-in
**關鍵差異**subnet route如 home-lan收到就自動進路由表`0.0.0.0/0` 不一樣 — client 收到後**不會自動啟用**,使用者要明確 select 才會把預設路由切過去:
**macOS / Linux CLI**
```bash
netbird routes list
# 看到兩條 exit-* 初始都是 "Not selected"
netbird routes select exit-virmach-lax # 切到美國出口
netbird routes select exit-ct100 # 或切回家裡出口
netbird routes deselect exit-virmach-lax # 取消,回成直連上網
```
**macOS App / iPhone App**tray / App → **Exit Node** → 選一個或 `None`。UI 是 radio button同時選兩條 `0.0.0.0/0` 沒意義metric 一樣路由表只留一條)。
### 驗證出口 IP
```bash
# 選 exit-virmach-lax 後
curl -4 ifconfig.me
# → 141.11.93.252
# 選 exit-ct100 後
curl -4 ifconfig.me
# → 125.229.110.50
# deselect 或 None
curl -4 ifconfig.me
# → 本地網路的出口 IP咖啡店的、機場的等
```
### 看不到 exit route 的排查
Client 已連線但 `netbird routes list` 沒有 `exit-*`
1. Daemon 啟動早於 route 建立 → 重啟 daemon見 `peer-deployment-ops.md` 的 launchctl / systemctl 段)
2. 該 peer 不在 route 的 `groups`distribution裡 — 目前只 `personal` 拿得到,`servers` 看不到
3. Policy 擋住 — `access_control_groups` 必須包含 client 所在 group
### 額外風險(相對於 subnet router
- **Exit peer 看得到所有流量的元資料**HTTPS 內容仍 E2E 加密,但 DNS 查詢、SNI、流量時序全部對 routing peer 可見
- **virmach-lax 是第三方 VPS**VirMach 營運者理論上可在 host 上 tcpdump `wt0`。比 CT100自家硬體多一層信任假設。敏感操作銀行、公司 VPN仍建議走 `None` 或 `exit-ct100`
- **不要讓 servers group 拿到 `0.0.0.0/0`**:伺服器自己繞 exit node 出網會產生 loop 或不必要延遲
## 安全考量
**Routing Peer 被攻破 = 攻擊者能透過 overlay 打進家裡所有 LAN 設備。**
降低風險:
- 帶路人只開最小必要服務(本部署 CT100 就是純 jump box表面積小
- 透過 `access_control_groups` 限制哪些 group 能走這條 route
- 考慮把 route 拆細:例如 `192.168.42.10/32`(只 OpenWrt、`192.168.42.40/30`(只 PVE 節點)各自一條 route不要整段開
- 未來可加上 port-specific 的 policy例如 personal → home-lan 只開 22/443
## 相關文件
- `groups-and-policies.md` — Network Route 用 group 控管存取
- `peer-deployment-ops.md` — Tailscale/NetBird iptables 衝突(選 routing peer 的考量)