Mac app actually lets you tick both 0.0.0.0/0 routes at once; kernel picks one by insertion order (undefined), not load-balanced. Warn against it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
310 lines
12 KiB
Markdown
310 lines
12 KiB
Markdown
# Network Routes(Subnet 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 Route(API)
|
||
|
||
```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 app(VLC 通常 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 衝突章節。
|
||
|
||
**本部署避開這問題的方式:選 CT100(Tailscale 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** → 勾選要用的那條,或按 `Deselect All` 不走 exit node。
|
||
|
||
⚠️ UI 是 **checkbox 不是 radio button**,可以同時勾兩條 — 但兩條都是 `0.0.0.0/0` metric 9999,kernel 只會用其中一條當 default route(依插入順序),不會負載均衡。實際出口用哪個是 undefined,建議只勾一條,或 `Deselect All`。
|
||
|
||
### 驗證出口 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 的考量)
|