# 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 → `,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="" SERVERS_GID="" 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 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="" SERVERS_GID="" 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 的考量)