---
title: 家庭网络架构实战：有线 Mesh、单臂旁路由与 Fail-Open
url: https://doc.liz6.com/homelab/network-architecture
locale: zh
area: homelab
tags:
- 家庭网络
- Mesh
- 旁路由
- OpenWrt
- mihomo
- nftables
- homelab
date: 2026-08-11
modified: 2026-08-11
description: 一套家庭网络架构实战：光猫桥接、有线 Mesh、N100 单臂旁路由、AdGuard Home 与 mihomo 分流，以及代理组件故障时自动直连的 Fail-Open 设计。
---

# 家庭网络架构实战：有线 Mesh、单臂旁路由与 Fail-Open

这套网络的目标不是堆设备，而是同时解决三个问题：多房间稳定覆盖、按设备选择代理路径，以及旁路由故障时不拖垮全屋网络。最终结构是：光猫桥接 → 主路由拨号 → 4 个 AP 有线回传；N100 单网口设备承担透明代理和 DNS，核心工作站则绕过旁路由，自行管理代理。

本文既是架构复盘，也是可以照着检查自己网络的设计说明。文中的主机名、SSID、订阅地址和公网节点均已泛化；`.1`、`.7`、`.8` 等仅用于说明同一私网内的角色关系。

## 设计结论先行

- Mesh 节点尽量使用有线回传，无线漫游不能弥补回传链路本身的拥塞。
- 单臂旁路由的流量会从同一个 LAN 接口进出，回程对称和 ICMP Redirect 必须单独处理。
- DNS、透明代理和 DHCP 网关耦合后，应设计 Fail-Open；代理坏了可以降级，不能让全屋断网。
- 关键工作站可以直连主路由并运行自己的 TUN，与全屋透明代理隔离故障域。
- 配置应从一份真源派生，目标端只维护自己的 DNS、TUN 和入站差异。

## 一、物理拓扑

光猫只做光电转换(桥接,不拨号不路由不 NAT),公网 IP 直落主路由 PPPoE 接口,无双重 NAT。全屋约 120m² 四室,单 AP 无法覆盖所有房间(尤其主卧和次卧隔两道承重墙),4 AP 有线 Mesh 覆盖死角 + 统一 SSID 无缝漫游。全部 AP 走有线回传,不占用无线带宽。

<svg viewBox="0 0 780 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="家庭网络物理拓扑">
  <rect width="780" height="420" fill="#ffffff"/>
  <text x="390" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">家庭网络物理拓扑(v2.1) · 120m² 四室 · 4 AP 有线 Mesh 覆盖</text>
  <!-- Internet -->
  <rect x="354" y="46" width="72" height="24" rx="12" fill="#e2e8f0"/>
  <text x="390" y="62" text-anchor="middle" font-size="11" fill="#475569">Internet</text>
  <line x1="390" y1="70" x2="390" y2="86" stroke="#94a3b8" stroke-width="1.5"/>
  <!-- GPON -->
  <rect x="330" y="86" width="120" height="30" rx="6" fill="#f1f5f9" stroke="#cbd5e1"/>
  <text x="390" y="106" text-anchor="middle" font-size="11" fill="#475569">GPON · 电信</text>
  <line x1="390" y1="116" x2="390" y2="132" stroke="#94a3b8" stroke-width="1.5"/>
  <rect x="325" y="132" width="130" height="30" rx="6" fill="#e2e8f0" stroke="#94a3b8"/>
  <text x="390" y="151" text-anchor="middle" font-size="11" fill="#334155">ONT 光猫 · 桥接</text>
  <line x1="390" y1="162" x2="390" y2="182" stroke="#475569" stroke-width="1.8"/>
  <!-- Main router -->
  <rect x="285" y="182" width="210" height="42" rx="8" fill="#dbeafe" stroke="#3b82f6"/>
  <text x="390" y="200" text-anchor="middle" font-size="13" font-weight="700" fill="#1e40af">主路由 .1 (RP01)</text>
  <text x="390" y="216" text-anchor="middle" font-size="10" fill="#1d4ed8">PPPoE 拨号 · NAT · DHCP · Mesh root</text>
  <!-- trunk line -->
  <line x1="390" y1="224" x2="390" y2="248" stroke="#475569" stroke-width="1.5"/>
  <line x1="390" y1="248" x2="120" y2="270" stroke="#475569" stroke-width="1.5"/>
  <line x1="390" y1="248" x2="300" y2="270" stroke="#475569" stroke-width="1.5"/>
  <line x1="390" y1="248" x2="480" y2="270" stroke="#475569" stroke-width="1.5"/>
  <line x1="390" y1="248" x2="660" y2="270" stroke="#475569" stroke-width="1.5"/>
  <text x="390" y="264" text-anchor="middle" font-size="10" fill="#64748b">全有线回传</text>
  <!-- APs: wider spread each ~160px apart -->
  <rect x="36" y="274" width="164" height="44" rx="6" fill="#dcfce7" stroke="#86efac"/>
  <text x="118" y="294" text-anchor="middle" font-size="11" fill="#166534">客厅 .62</text>
  <text x="118" y="309" text-anchor="middle" font-size="9" fill="#4ade80">RC06 · 入户区</text>
  <rect x="210" y="274" width="164" height="44" rx="6" fill="#dcfce7" stroke="#86efac"/>
  <text x="292" y="294" text-anchor="middle" font-size="11" fill="#166534">主卧 .67</text>
  <text x="292" y="309" text-anchor="middle" font-size="9" fill="#4ade80">RP03 · 隔两墙</text>
  <rect x="384" y="274" width="164" height="44" rx="6" fill="#dcfce7" stroke="#86efac"/>
  <text x="466" y="294" text-anchor="middle" font-size="11" fill="#166534">书房 .80</text>
  <text x="466" y="309" text-anchor="middle" font-size="9" fill="#4ade80">RP03 · N100 挂这里</text>
  <rect x="558" y="274" width="164" height="44" rx="6" fill="#dcfce7" stroke="#86efac"/>
  <text x="640" y="294" text-anchor="middle" font-size="11" fill="#166534">次卧 .227</text>
  <text x="640" y="309" text-anchor="middle" font-size="9" fill="#4ade80">RP03 · 最远端</text>
  <!-- N100 hangs off 书房 -->
  <line x1="466" y1="318" x2="466" y2="344" stroke="#f59e0b" stroke-width="1.8"/>
  <rect x="370" y="344" width="192" height="34" rx="6" fill="#fef3c7" stroke="#f59e0b"/>
  <text x="466" y="360" text-anchor="middle" font-size="11" font-weight="700" fill="#b45309">N100 .7 (旁路由)</text>
  <text x="466" y="373" text-anchor="middle" font-size="9" fill="#d97706">单网口 · 挂书房 AP LAN</text>
  <!-- workstation WiFi -->
  <line x1="466" y1="318" x2="380" y2="392" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4 6"/>
  <rect x="310" y="390" width="100" height="18" rx="4" fill="#f8fafc"/>
  <text x="360" y="403" text-anchor="middle" font-size="10" fill="#94a3b8">工作站 .8 (WiFi)</text>
</svg>

## 二、Mesh 网络

| 节点 | IP | 硬件 | 位置 | 覆盖难点 | 回传 |
|------|-----|------|------|----------|------|
| root | .1 | RP01 | 入户 | 客厅中心 | — |
| leaf | .62 | RC06 | 客厅 | 覆盖入户区 | 有线 |
| leaf | .67 | RP03 | 主卧 | 隔两道承重墙 | 有线 |
| leaf | .80 | RP03 | 书房 | 核心设备区 | 有线 |
| leaf | .227 | RP03 | 次卧 | 最远,单 AP 无信号 | 有线 |

无线侧使用一个主 SSID（5 GHz，日常设备）和一个独立的 2.4 GHz IoT SSID。IoT 网络是否隐藏并不构成安全边界；真正的隔离应依靠访客网络、VLAN 或防火墙策略。

## 三、关键主机

| 设备 | IP | 角色 | 连接 | 系统 |
|------|-----|------|------|------|
| ONT | — (桥接,无 IP) | 光电转换 | 光纤入,以太网出 | 电信 GPON |
| 小米主路由 | .1 | PPPoE/NAT/DHCP/Mesh root | WAN 接光猫 | MiWiFi(RP01) |
| N100 旁路由 | .7 | 透明代理 + DNS（AdGuard Home） | 有线单网口，挂书房 AP LAN | iStoreOS |
| 核心工作站 | .8 | 开发与自托管服务，**静态网关指向 .1** | Wi-Fi，连接书房 AP | Linux |
| 旧节点 | .9 | 退役旁路由，仅保留维护入口 | Wi-Fi | Windows |

> 关键设计：核心工作站（.8）的静态网关指向主路由（.1），不受旁路由透明代理影响。它自行运行 TUN 模式的 mihomo，并使用返回真实地址的 DNS，因此与普通家庭设备形成两个独立的故障域。

## 四、DHCP 与 DNS

- **网关**：DHCP 默认下发 .7（N100），普通设备经旁路由转发。核心工作站例外，静态网关指向 .1。
- **DNS**：DHCP 只发布 .7（N100 上的 AdGuard Home）。不要把公网 DNS 作为“备用”同时下发，因为客户端可以绕过主 DNS，过滤与分流结果会变得不可预测。组件故障时由 Fail-Open 临时接管 :53。
- **租约**：1–2 小时。较短租约便于修改网关配置后加快收敛，但不能替代网关高可用。
- **地址池**:192.168.31.5–250。

### 正常 DNS 链路

<svg viewBox="0 0 720 210" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="DNS 链路">
  <defs><marker id="dnsah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="210" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">DNS 链路:客户端 → AdGuard → mihomo fake-ip 分流 → 上游</text>
  <!-- Client -->
  <rect x="30" y="56" width="80" height="30" rx="6" fill="#e2e8f0"/><text x="70" y="76" text-anchor="middle" font-size="12" fill="#334155">客户端</text>
  <line x1="110" y1="71" x2="148" y2="71" stroke="#475569" stroke-width="1.5" marker-end="url(#dnsah)"/>
  <!-- AdGuard -->
  <rect x="152" y="48" width="140" height="46" rx="8" fill="#dbeafe" stroke="#3b82f6"/>
  <text x="222" y="67" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">AdGuard :53</text>
  <text x="222" y="84" text-anchor="middle" font-size="9" fill="#3b82f6">广告过滤 + 缓存 · Docker</text>
  <line x1="292" y1="71" x2="340" y2="71" stroke="#475569" stroke-width="1.5" marker-end="url(#dnsah)"/>
  <!-- mihomo -->
  <rect x="344" y="48" width="140" height="46" rx="8" fill="#fef3c7" stroke="#f59e0b"/>
  <text x="414" y="67" text-anchor="middle" font-size="12" font-weight="700" fill="#b45309">mihomo :1053</text>
  <text x="414" y="84" text-anchor="middle" font-size="9" fill="#d97706">fake-ip 分流</text>
  <!-- branch -->
  <line x1="484" y1="58" x2="530" y2="50" stroke="#475569" stroke-width="1.5" marker-end="url(#dnsah)"/>
  <line x1="484" y1="84" x2="530" y2="92" stroke="#475569" stroke-width="1.5" marker-end="url(#dnsah)"/>
  <!-- domestic -->
  <rect x="534" y="34" width="160" height="32" rx="6" fill="#dcfce7" stroke="#86efac"/>
  <text x="614" y="54" text-anchor="middle" font-size="11" fill="#166534">国内: UDP 223.5.5.5</text>
  <!-- international -->
  <rect x="534" y="78" width="160" height="32" rx="6" fill="#ffedd5" stroke="#fdba74"/>
  <text x="614" y="98" text-anchor="middle" font-size="11" fill="#9a3412">国外: DoH 1.1.1.1</text>
  <!-- bottom notes -->
  <rect x="60" y="130" width="600" height="66" rx="8" fill="#f8fafc" stroke="#e2e8f0"/>
  <text x="76" y="152" font-size="11" fill="#475569">fake-ip 模式下,AdGuard 收上游响应全是 198.18.x 段,mihomo 内部映射还原真实 IP。</text>
  <text x="76" y="170" font-size="11" fill="#475569">本方案不在 AdGuard 层验证 DNSSEC；fake-ip 是合成响应，启用前需单独验证整条解析链。</text>
  <text x="76" y="188" font-size="11" fill="#475569">DHCP 仅发布 AdGuard；是否存在 DoH/DoT 绕过仍需通过流量观测确认。</text>
</svg>

## 五、流量路径

### 正常模式

<svg viewBox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="正常流量路径">
  <defs><marker id="fah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="320" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">流量路径:旁路由代理 + 回国直连双路径</text>
  <!-- Client -->
  <rect x="30" y="56" width="90" height="32" rx="6" fill="#e2e8f0"/>
  <text x="75" y="76" text-anchor="middle" font-size="12" fill="#334155">客户端</text>
  <line x1="120" y1="72" x2="168" y2="72" stroke="#475569" stroke-width="1.5" marker-end="url(#fah)"/>
  <!-- N100 -->
  <rect x="172" y="46" width="210" height="52" rx="8" fill="#fef3c7" stroke="#f59e0b"/>
  <text x="277" y="66" text-anchor="middle" font-size="13" font-weight="700" fill="#b45309">N100 (.7) · nftables prerouting</text>
  <text x="277" y="83" text-anchor="middle" font-size="10" fill="#d97706">发夹弯:MASQUERADE 改源→.7</text>
  <!-- Branch top: CN -->
  <line x1="382" y1="56" x2="430" y2="56" stroke="#475569" stroke-width="1.5" marker-end="url(#fah)"/>
  <rect x="434" y="40" width="130" height="32" rx="6" fill="#dcfce7" stroke="#86efac"/>
  <text x="499" y="60" text-anchor="middle" font-size="11" fill="#166534">① 大陆 IP</text>
  <line x1="499" y1="72" x2="499" y2="96" stroke="#16a34a" stroke-width="1.2"/>
  <rect x="434" y="96" width="130" height="40" rx="6" fill="#f0fdf4"/>
  <text x="499" y="113" text-anchor="middle" font-size="10" fill="#166534">防火墙 return</text>
  <text x="499" y="128" text-anchor="middle" font-size="9" fill="#4ade80">不进 mihomo,直连回国</text>
  <!-- Branch bottom: 其余 -->
  <line x1="382" y1="88" x2="430" y2="120" stroke="#475569" stroke-width="1.5" marker-end="url(#fah)"/>
  <rect x="434" y="150" width="130" height="32" rx="6" fill="#dbeafe" stroke="#60a5fa"/>
  <text x="499" y="170" text-anchor="middle" font-size="11" fill="#1e40af">② 其余流量</text>
  <line x1="499" y1="182" x2="499" y2="200" stroke="#3b82f6" stroke-width="1.2"/>
  <rect x="434" y="200" width="130" height="48" rx="6" fill="#eff6ff"/>
  <text x="499" y="218" text-anchor="middle" font-size="10" fill="#1e40af">mihomo 分流</text>
  <text x="499" y="233" text-anchor="middle" font-size="9" fill="#3b82f6">国内直连 / 国外走代理</text>
  <!-- converge -->
  <line x1="564" y1="116" x2="615" y2="116" stroke="#94a3b8" stroke-width="1.2"/>
  <line x1="564" y1="224" x2="615" y2="224" stroke="#94a3b8" stroke-width="1.2"/>
  <rect x="619" y="144" width="72" height="28" rx="6" fill="#dbeafe"/>
  <text x="655" y="162" text-anchor="middle" font-size="11" fill="#1e40af">主路由 .1</text>
  <line x1="655" y1="172" x2="655" y2="194" stroke="#475569" stroke-width="1.5" marker-end="url(#fah)"/>
  <rect x="600" y="198" width="110" height="26" rx="6" fill="#e2e8f0"/>
  <text x="655" y="216" text-anchor="middle" font-size="10" fill="#475569">PPPoE → 公网</text>
  <!-- workstation bypass -->
  <rect x="30" y="234" width="90" height="28" rx="6" fill="#f1f5f9" stroke-dasharray="3 5"/>
  <text x="75" y="252" text-anchor="middle" font-size="11" fill="#64748b">工作站 (.8)</text>
  <line x1="120" y1="248" x2="615" y2="248" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="4 5"/>
  <text x="370" y="264" text-anchor="middle" font-size="9" fill="#94a3b8">网关→.1,直连主路由,不经旁路由</text>
  <!-- bottom notes -->
  <rect x="60" y="282" width="600" height="28" rx="6" fill="#f8fafc"/>
  <text x="360" y="301" text-anchor="middle" font-size="10" fill="#64748b">① 大陆 IP 靠 china_ip 集合(~3.7k chnroute)在防火墙 prerouting 就 return,物理上不进 mihomo,回国流量硬防护。</text>
</svg>

> **① 大陆 IP 在防火墙层 return,物理上不进 mihomo**——回国流量硬防护。单网口发夹弯(同 br-lan 进出)转发回 .1,**必须 MASQUERADE 改源成 .7**,否则回程不对称直接断网。
>
> **② fake-ip 漏命中 geosite 的 CN 域名**（拿到 198.18.0.0/15 地址）仍会进入 mihomo，可由规则尾部的 `GEOIP,CN,DIRECT` 在解析真实地址后兜底。核心工作站使用自己的 TUN 和真实地址 DNS，不依赖这条路径。

### Fail-Open 模式(mihomo/AdGuard 挂了)

<svg viewBox="0 0 720 210" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Fail-Open 模式">
  <defs><marker id="foah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#ea580c"/></marker></defs>
  <rect width="720" height="210" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Fail-Open:代理组件故障时降级为直连</text>
  <!-- Client -->
  <rect x="24" y="54" width="76" height="28" rx="6" fill="#e2e8f0"/><text x="62" y="73" text-anchor="middle" font-size="11" fill="#334155">客户端</text>
  <line x1="100" y1="68" x2="152" y2="68" stroke="#ea580c" stroke-width="1.8" marker-end="url(#foah)"/>
  <!-- N100 failopen -->
  <rect x="156" y="44" width="210" height="48" rx="8" fill="#fef2f2" stroke="#f87171"/>
  <text x="261" y="63" text-anchor="middle" font-size="12" font-weight="700" fill="#b91c1c">N100 (.7) · 仍是网关</text>
  <text x="261" y="80" text-anchor="middle" font-size="9" fill="#dc2626">nft redirect 已移除 · MASQ 注入</text>
  <line x1="366" y1="68" x2="428" y2="68" stroke="#ea580c" stroke-width="1.8" marker-end="url(#foah)"/>
  <!-- Main router -->
  <rect x="432" y="48" width="120" height="40" rx="8" fill="#dbeafe" stroke="#3b82f6"/>
  <text x="492" y="66" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">主路由 .1</text>
  <text x="492" y="80" text-anchor="middle" font-size="9" fill="#3b82f6">PPPoE · DNS 接管</text>
  <line x1="552" y1="68" x2="614" y2="68" stroke="#475569" stroke-width="1.5" marker-end="url(#foah)"/>
  <rect x="618" y="54" width="72" height="28" rx="12" fill="#e2e8f0"/><text x="654" y="73" text-anchor="middle" font-size="11" fill="#475569">公网</text>
  <!-- bottom -->
  <rect x="80" y="106" width="560" height="56" rx="8" fill="#fff7ed" stroke="#fdba74"/>
  <text x="100" y="128" font-size="11" fill="#9a3412">触发条件:mihomo 或 AdGuard 进程异常 → nikki-failopen(procd)自愈</text>
  <text x="100" y="146" font-size="11" fill="#9a3412">行为:nftables redirect 整表删除 → 全流量直通主路由 → DNS :53 重定向到主路由</text>
  <text x="360" y="176" text-anchor="middle" font-size="12" font-weight="600" fill="#c2410c">无代理 · 无广告过滤 · 全直连 —— 降级保底,不断网</text>
</svg>

这里的 Fail-Open 有明确边界：它依赖 N100 上仍能运行的守护进程，只覆盖 mihomo 或 AdGuard Home 退出、卡死等软件故障。N100 断电、内核死机或 LAN 链路中断时，`.7` 作为默认网关会整体不可达，仍会导致普通客户端断网。要覆盖这一层故障，需要 VRRP/双机热备，或由主路由健康检查后自动改发 DHCP 网关；当前架构没有实现。

## 六、分流策略与回国流量防护

曾发生 CN 域名漏命中 geosite → `MATCH,PROXY` → 美国节点回连国内服务器 → GFW 识别"回国流量"封 VPS IP。现采用**双层防护**:

**第一层(硬,防火墙)**:`bypass_china_mainland_ip=1`(v4)填充 nft `china_ip` 集合(~3.7k chnroute)。大陆 IP 在 prerouting 就 `return`,物理上到不了 mihomo,与域名是否命中 geosite 无关。
- 单网口发夹弯需 `table inet bypass_masq` 把转发流量 MASQUERADE 改源成 .7。
- v6 暂不开防火墙旁路(避免 v6 发夹弯翻车),v6 CN 走 mihomo `GEOIP,CN` 兜底。

**第二层(兜底,mihomo 规则)**:规则尾 `GEOSITE,cn,DIRECT` → `GEOIP,CN,DIRECT` → `MATCH,PROXY`。fake-ip 漏网的 CN 域名进 mihomo 后,`GEOIP,CN` 解析真实 IP 拦截。

### 关键 sysctl

| 参数 | 值 | 原因 |
|------|----|----|
| `net.ipv4.conf.*.send_redirects` | **0** | 否则 .7 发 ICMP 重定向,客户端绕过 .7 直连 .1 形成"双路径":同目标客户端直连 + mihomo 代拨同时存在,经主路由同一公网 IP NAT 后触发服务器 **PAWS** 丢弃时间戳较小的那条 → i/o timeout 风暴(闲鱼等 App 卡死真凶)。**对所有单臂/发夹弯旁路由方案都适用**,不只是本机 |
| `net.ipv4.conf.*.rp_filter` | 0 | 允许非对称,配合发夹弯 |

### 出国线路

三台 VPS 分布在不同网络线路上,延迟和稳定性差异来自回国线路的 QoS 等级:

| 线路 | 全称 | 优先级 | 特点 | 适合 |
|------|------|--------|------|------|
| **CN2 GIA** | China Telecom Next Gen Carrier | 最高 | 电信顶级线路,全程 CN2 骨干不经过 163 网,QoS 最低,晚高峰基本不掉速 | 对延迟/稳定性有要求的主力节点 |
| **9929** | China Unicom Premium | 中高 | 联通精品线路,类似 CN2 GIA 但走联通骨干,北方联通宽带用户最优 | 联通用户直连最优 |
| **4837** | China Unicom Standard | 普通 | 联通标准国际线路,晚高峰拥堵明显但价格便宜 | 备线/冷备/预算敏感 |

### 节点与协议如何选

不在架构文档里固化供应商、套餐价格或某次测速结果：这些信息变化快，也无法代表不同地区、运营商和晚高峰时段的体验。更稳妥的选型方法是用自己的接入网络持续测量：

1. 分别记录工作日白天和晚高峰的 RTT、抖动、丢包率与 TCP/UDP 吞吐。
2. 主节点和备节点选择不同上游或不同路由，避免看似多节点、实际共享同一故障域。
3. 同时测试 TCP 与 UDP 协议；协议表现取决于链路质量，不能从一次延迟测试推导长期结论。
4. 切换策略应考虑业务体验。自动切换适合节点质量接近的场景；质量差异很大时，手动切换更容易发现降级。
5. 把原始结果交给 Prometheus Blackbox Exporter 或同类探针保存，用一周以上的分位数而非瞬时最低值做决定。

这套网络选择手动 Selector：主节点异常时报警，由维护者确认后切换。代价是恢复不完全自动，收益是不会静默落到一个“能连通但体验很差”的节点。

### 提速项

- `tcp-concurrent: true`:并发拨号多解析 IP 取最快。
- QUIC reject 规则故意保留:`AND,(DST-PORT,443),(NETWORK,UDP),(NOT,GEOSITE,cn),REJECT` 强制外网 HTTP/3→h2,治"QUIC-over-Hy2 空闲后复用挂死",放开会复发。

> **IPv6 为什么不开硬旁路**:v4 硬旁路的关键是发夹弯 MASQUERADE(改源成 .7 保证回程对称),这个操作依赖 N100 有固定的内网 v4 地址。但 v6 地址是 SLAAC 动态分配的,没有稳定的内网地址做 SNAT 锚点——发夹弯的回程对称在 v6 上无法可靠实现,强行开只会随机断流。所以 v6 CN 当前只靠 mihomo 规则层 `GEOIP,CN` 兜底,不走防火墙 prerouting 硬旁路。
> **监控**：N100、核心工作站和主路由的关键组件接入 Prometheus + Grafana，见 [monitoring.md](monitoring.md)。节点可用性由 Prometheus Blackbox Exporter 探测；这里保留人工确认后的切换策略。

## 七、N100 关键组件

| 组件 | 地址/路径 | 说明 |
|------|-----------|------|
| mihomo 内核 | `/usr/bin/mihomo`,procd via `/etc/init.d/nikki` | 透明代理+TUN |
| mihomo API | `127.0.0.1:9090` | 健康探测 |
| mihomo mixed | `:7890`(SOCKS5+HTTP) | 显式代理入口 |
| mihomo redir | `:7891` | 透明代理 redirect 入站 |
| mihomo DNS | `:1053`(fake-ip) | AdGuard 上游 |
| Nikki dashboard | `http://<side-router-ip>:9090/ui` | Web 控制台，仅限内网访问 |
| AdGuard Home | Docker host 网络，`http://<side-router-ip>:8083` | DNS 广告过滤，上游指向 :1053 |
| failopen daemon | `/usr/local/sbin/nikki-failopen.sh`,procd | 健康检查+自动 fail-open |
| CN 旁路 SNAT | `/etc/nikki/scripts/bypass-masq.{nft,sh}` + firewall include | 发夹弯 masquerade,持久化 |

> N100 迁移动机与硬件选型:原先旁路由跑在桌面机 WiFi 上,WiFi 抖动全屋断网;换 N100(低功耗 x86,iStoreOS,单千兆口)挂书房 AP LAN 口做有线旁路由,从此与 WiFi 解耦。

## 八、nftables 结构

**处理顺序**(prerouting → forward → postrouting):

<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="nftables 处理顺序:prerouting 到 postrouting 的包处理流水线">
  <defs><marker id="nfah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="420" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">nftables 处理顺序:prerouting → forward → postrouting</text>
  <!-- 入站包 -->
  <rect x="150" y="44" width="140" height="26" rx="13" fill="#e2e8f0"/>
  <text x="220" y="61" text-anchor="middle" font-size="12" fill="#334155">入站包</text>
  <line x1="220" y1="70" x2="220" y2="88" stroke="#475569" stroke-width="1.6" marker-end="url(#nfah)"/>
  <!-- fw4 forward -->
  <rect x="40" y="90" width="360" height="32" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="220" y="110" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">fw4 forward(policy drop)</text>
  <line x1="220" y1="122" x2="220" y2="140" stroke="#475569" stroke-width="1.6" marker-end="url(#nfah)"/>
  <!-- nikki dstnat/mangle_prerouting -->
  <rect x="40" y="142" width="360" height="44" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="220" y="160" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">nikki dstnat / mangle_prerouting</text>
  <text x="220" y="176" text-anchor="middle" font-size="9" fill="#4f46e5">透明代理入口 · 按目的地分流</text>
  <!-- branch lines -->
  <line x1="400" y1="164" x2="440" y2="154" stroke="#475569" stroke-width="1.4" marker-end="url(#nfah)"/>
  <line x1="400" y1="164" x2="440" y2="204" stroke="#475569" stroke-width="1.4" marker-end="url(#nfah)"/>
  <!-- branch: 大陆 IP/私网 -->
  <rect x="440" y="132" width="240" height="44" rx="6" fill="#dcfce7" stroke="#86efac"/>
  <text x="452" y="151" font-size="12" font-weight="700" fill="#166534">大陆 IP / 私网</text>
  <text x="452" y="168" font-size="10" fill="#15803d">→ return,不进代理(直连)</text>
  <!-- branch: 其余 -->
  <rect x="440" y="182" width="240" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="452" y="201" font-size="12" font-weight="700" fill="#115e59">其余流量</text>
  <text x="452" y="218" font-size="10" fill="#0f766e">→ DNAT :7891 或标记 TUN</text>
  <line x1="220" y1="186" x2="220" y2="206" stroke="#475569" stroke-width="1.6" marker-end="url(#nfah)"/>
  <!-- bypass_masq postrouting -->
  <rect x="40" y="208" width="360" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="220" y="226" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">bypass_masq postrouting</text>
  <text x="220" y="242" text-anchor="middle" font-size="9" fill="#4f46e5">发夹弯 SNAT 锚点</text>
  <line x1="400" y1="228" x2="440" y2="228" stroke="#475569" stroke-width="1.4" marker-end="url(#nfah)"/>
  <!-- branch: CN 直连流量 -->
  <rect x="440" y="206" width="240" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="452" y="225" font-size="12" font-weight="700" fill="#115e59">CN 直连流量</text>
  <text x="452" y="242" font-size="10" fill="#0f766e">→ MASQUERADE(src→.7,回程对称)</text>
  <line x1="220" y1="248" x2="220" y2="266" stroke="#475569" stroke-width="1.6" marker-end="url(#nfah)"/>
  <!-- fw4 srcnat_lan -->
  <rect x="40" y="268" width="360" height="32" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="220" y="288" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">fw4 srcnat_lan(docker MASQUERADE)</text>
  <line x1="220" y1="300" x2="220" y2="318" stroke="#475569" stroke-width="1.6" marker-end="url(#nfah)"/>
  <!-- 出站 -->
  <rect x="150" y="320" width="140" height="26" rx="13" fill="#e2e8f0"/>
  <text x="220" y="337" text-anchor="middle" font-size="12" fill="#334155">出站</text>
  <!-- insight -->
  <rect x="40" y="366" width="640" height="42" rx="8" fill="#f8fafc" stroke="#e2e8f0"/>
  <text x="56" y="384" font-size="11" fill="#475569">大陆 IP 在 prerouting 就 return,物理上不进 mihomo,与域名是否命中 geosite 无关。</text>
  <text x="56" y="400" font-size="11" fill="#475569">bypass_masq 独立成表,nikki 重启/fail-open 不影响回程对称的 SNAT 锚点。</text>
</svg>

### table inet fw4(OpenWrt 防火墙)
- `forward`:policy drop;放行 TUN 流量 + LAN→WAN 转发
- `srcnat_lan`:docker MASQUERADE

### table inet nikki(透明代理规则)
- `china_ip` 集合:~3.7k chnroute CIDR(由 `bypass_china_mainland_ip=1` 填充)
- `dstnat` / `mangle_prerouting_lan`:大陆 IP / 私网 / 198.18 命中 → `return` 旁路;其余 DNAT TCP→:7891 或标记 TUN
- `mangle_output`:本机出站 → TUN
- fail-open 时整表删除

### table inet bypass_masq(CN-IP 旁路的发夹弯 SNAT)
- `postrouting`(nat hook,priority srcnat+5):`iifname br-lan oifname br-lan ip daddr != {私网+198.18+组播} masquerade`
- 只命中被 china_ip 旁路、需转发回 .1 的公网 CN 直连流量,改源成 .7 保证回程对称
- 独立于 table nikki,**nikki 重启不影响**;持久化靠 `firewall.bypass_masq` include

### table inet failopen(故障注入,恢复时删除)
- `srcnat`：MASQUERADE `<lan-cidr>` → 非本地
- `dns`:DNAT :53 → .1:53

## 九、自托管服务与故障域

以下服务当前依赖核心工作站的 Wi-Fi。`wifi-watchdog`（systemd timer）可以恢复短暂的射频或驱动异常，但它不能把无线链路变成高可用基础设施；长期运行的入口和监控服务应迁移到有线节点。

| 服务 | 用途 | 可用性 |
|------|------|--------|
| DERP | Tailscale 中继 | 高 |
| rathole | 内网穿透(见 [rathole-tunnel.md](rathole-tunnel.md)) | 高 |
| paste/mdserve | 笔记分享 | 中 |
| Prometheus | 监控采集 | 中 |
| sunshine | 游戏串流 | 低 |
| 本地 LLM | AI 推理(见 [local-llm.md](local-llm.md)) | 低 |

## 十、守护进程

| 守护 | 位置 | 场景 |
|------|------|------|
| nikki-failopen | N100(procd) | mihomo/AdGuard 进程异常 |
| 通电自启 | N100(BIOS) | 停电恢复 |
| wifi-watchdog | 核心工作站（systemd timer） | Wi-Fi 驱动/射频异常 |
| sync-mihomo-to-router | 核心工作站（systemd timer） | 真源→旁路由配置漂移 |
| sync-subscribe | 核心工作站（systemd timer） | 真源→移动端配置漂移 |

### 配置派生流水线

家里三处需要 mihomo 配置：核心工作站（TUN 自代理）、N100（全屋透明代理）和移动端导入文件。三份的**节点列表、代理分组、分流规则必须一致**，但 DNS、TUN、入站方式各不相同。如果各自独立维护必然漂移。

**方案**：只手动编辑 `<config-root>/config.yaml`，其中包含 `proxies`、`proxy-groups` 和 `rules`；两条 timer 驱动的脚本从它派生目标配置。

**#1 本机**:直接使用真源。TUN device `Meta` + 本地 AdGuard + fake-ip + controller。

**#2 N100 旁路由**(`sync-mihomo-to-router.sh`,timer 03:45):
- 从真源只抽 `proxies`/`proxy-groups`/`rules` 三段 → `/etc/nikki/profiles/home.yaml`
- 本机 `mihomo -t` 校验真源 → scp 到 N100 → `nikki -t` 校验 → 旧档备份(带时间戳)→ 校验失败自动回滚 → reload Nikki
- DNS/TUN/sniffer 由 N100 本地 UCI mixin 注入,不进 profile——这些是目标端特有,不应从真源覆盖

**#3 移动端配置**（`sync-subscribe.sh`）：
- 输出 `clash.yaml`(Clash 导入)+ `sub.txt`(v2rayN base64 分享链接)
- 脱本机化消毒:剔除 `external-controller`+`secret`(泄漏即被控)、`tun` 整段(手机不需要)、`nameserver:127.0.0.1`(漫游设备到不了)、`PROCESS-NAME` 规则(手机永不匹配)
- DNS 替换为 fake-ip + 公网 DoH,国外域名走出口解析防泄漏
- 若通过公网分发，入口使用独立域名和不可预测的路径令牌；Web 服务器只暴露派生文件，不暴露真源、控制器密钥或目录列表

两条 timer 错开运行，避免同时读取或写入目标配置。修改真源后也可手动执行对应脚本；脚本必须保持幂等，并在替换前完成语法校验和备份。

## 十一、上线后的验证清单

先把旁路由地址放进环境变量，避免示例命令里出现真实地址：

```bash
SIDE_ROUTER_IP=192.168.31.7
```

正常模式至少验证以下项目：

```bash
# 客户端的默认路由是否指向旁路由
ip route show default

# DNS 是否只经过 AdGuard，并能得到预期的 fake-ip 响应
dig @"$SIDE_ROUTER_IP" example.com

# 在旁路由上确认转发、重定向与 SNAT 规则存在
ssh "root@$SIDE_ROUTER_IP" 'nft list ruleset'

# 两个进程及其监听端口是否存活
ssh "root@$SIDE_ROUTER_IP" 'pgrep -a mihomo; ss -lntup'
```

Fail-Open 应在维护窗口演练，而不是等故障发生后猜测。分别停止 mihomo 和 AdGuard Home，确认守护进程撤销透明代理规则、接管 DNS，客户端仍能直连；随后恢复服务，并确认规则没有重复注入。最后单独模拟 N100 断电，验证监控确实报警，同时接受当前架构下普通客户端会断网这一事实。

| 场景 | 预期结果 |
|------|----------|
| mihomo 进程退出 | 自动撤销重定向，降级为直连 |
| AdGuard Home 进程退出 | :53 被接管到可用 DNS，客户端继续解析 |
| N100 重启后恢复 | 配置只注入一次，路由和 DNS 回到正常模式 |
| N100 断电或网口掉线 | 当前无网关热备，普通客户端断网并触发告警 |
| 核心工作站离线 | 普通家庭设备不受影响；自托管服务离线 |

## 十二、已知风险

| 风险 | 影响 | 缓解 |
|------|------|------|
| **核心工作站 Wi-Fi 单点** | 隧道与中继等自托管服务同时离线 | 长期迁到有线节点；watchdog 只作为短期止血 |
| **N100 单网口发夹弯** | 单臂拓扑回程不对称必断 | MASQUERADE + bypass_masq nft 表保障回程对称 |
| **mihomo/AdGuard 进程挂** | 全屋断网(DHCP 网关仍指向 .7) | nikki-failopen(procd)进程级自愈 |
| **N100 硬件、电源或链路故障** | 默认网关不可达，普通客户端断网 | 当前仅监控告警；后续引入双机网关或主路由侧自动切换 |
| **主路由硬件/固件故障** | 全屋断网(PPPoE+NAT+DHCP 全挂) | 暂无热备;原计划 N100 做主路由未落地 |
| **光猫桥接** | ONT 只做光电转换,不拨号不路由不 NAT,公网 IP 直落主路由 | 基本不是故障源,无双重 NAT |

> 详细评估和恢复流程见 [network-recovery.md](network-recovery.md)。

## 变更记录

| 日期 | 变更 |
|------|------|
| 2026-06-21 | N100 旁路由上线，旧工作站旁路由退役；DHCP 网关/DNS 从 .8 切到 .7；部署 Fail-Open 与 Wi-Fi watchdog |
| 2026-06-24 | 公网订阅修复:原样照搬本机 config 导致泄漏本机 DNS+管理口 secret+tun,sync-subscribe.sh 加脱本机化消毒;旁路由 profile 重跑刷新;新增 sync-mihomo-to-router.timer |
| 2026-06-27 | 关 ICMP `send_redirects`:治闲鱼等 App 卡死(双路径 PAWS 丢包风暴根因);CN-IP 防火墙旁路上线(`bypass_china_mainland_ip=1` + `table inet bypass_masq`);`tcp-concurrent` + CN DNS 改明文 UDP 223.5.5.5;节点洞察 |
| 2026-08-11 | 公开版文档泛化主机、SSID、订阅入口与节点信息；补充设计原则和验证方法 |

## 相关文档

- [rathole-tunnel.md](rathole-tunnel.md) — 隧道到家方案
- [network-recovery.md](network-recovery.md) — 故障恢复手册
- [monitoring.md](monitoring.md) — 监控架构
