49 min read #homelab
On this page

Home Network Architecture Documentation

TL;DR: Telecom GPON bridged → Xiaomi main router PPPoE dial-up → Whole-house wired Mesh (4 APs) → N100 single-port side router (mihomo + AdGuard + CN-IP hard bypass) → li-home-0 directly connected to main router + rathole for external exposure. Default traffic for the whole house goes through the proxy; core desktop traffic goes direct.

Topology version v2.1, this document serves as the single source of truth for whole-house network operations.

1. Physical Topology

The optical modem only performs optical-electrical conversion (bridged mode, no dial-up, no routing, no NAT). The public IP goes directly to the main router's PPPoE interface, avoiding double NAT. The house is approximately 120m² with four rooms. A single AP cannot cover all rooms (especially the master and secondary bedrooms, which are separated by two load-bearing walls). The 4 AP wired Mesh covers dead zones + provides unified SSID seamless roaming. All APs use wired backhaul, not consuming wireless bandwidth.

Home Network Physical Topology (v2.1) · 120m² 4 Rooms · 4 AP Wired Mesh Coverage Internet GPON · Telecom ONT Optical Modem · Bridged Main Router .1 (RP01) PPPoE Dial-up · NAT · DHCP · Mesh root All Wired Backhaul Living Room .62 RC06 · Entrance Area Master Bed .67 RP03 · Two Walls Away Study .80 RP03 · N100 Hangs Here Secondary Bed .227 RP03 · Furthest End N100 .7 (Side Router) Single Port · Hangs on Study AP LAN li-home-0 .8 (WiFi)

2. Mesh Network

NodeIPHardwareLocationCoverage ChallengeBackhaul
root.1RP01EntranceLiving room center
leaf.62RC06Living roomCovers entrance areaWired
leaf.67RP03Master bedroomSeparated by two load-bearing wallsWired
leaf.80RP03StudyCore equipment areaWired
leaf.227RP03Secondary bedroomFurthest, no signal from single APWired

SSID: 308_6G_WIFI (5G, daily use) + Hidden (2.4G, IoT dedicated).

3. Key Hosts

DeviceIPRoleConnectionOS
ONT— (Bridged, no IP)Optical-electrical conversionFiber in, Ethernet outTelecom GPON
Xiaomi Main Router.1PPPoE/NAT/DHCP/Mesh rootWAN connected to modemMiWiFi (RP01)
li-home-router.7Side router: Transparent proxy + DNS (AdGuard)Wired single port, hangs on Study AP LANiStoreOS (N100)
li-home-0.8Desktop: Dev + External services, Static IP Gateway→.1, no proxyWiFi (MT7927), connects to Study APGentoo
li-home-1.9Retired side router, SSH onlyWiFiWin11

Key Design: li-home-0 (.8) is statically configured with its gateway pointing to the main router (.1). The side router's transparent proxy does not cover it—this machine runs its own TUN mode mihomo for self-proxying. Its DNS uses real IP architecture, and it follows a different proxy path than other devices in the house.

4. DHCP & DNS

  • Gateway: DHCP defaults to issuing .7 (N100), so all house traffic goes through the side router proxy. Exception: li-home-0 (.8) is statically configured with gateway→.1.
  • DNS: Primary .7 (N100 AdGuard), with public DNS backup as fallback.
  • Lease: 1–2 hours. Shortening this accelerates DHCP recovery in case of main router failure.
  • Address Pool: 192.168.31.5–250.

Normal DNS Path

DNS Path: Client → AdGuard → mihomo fake-ip split → Upstream Client AdGuard :53 Ad Filtering + Cache · Docker mihomo :1053 fake-ip Split Domestic: UDP 223.5.5.5 International: DoH 1.1.1.1 In fake-ip mode, AdGuard receives upstream responses all in the 198.18.x range; mihomo internally maps and restores the real IP. ⚠ Do not enable DNSSEC in AdGuard — fake-ip breaks the DNSSEC trust chain, causing all domains to be flagged as hijacked and rejected. ✅ Downstream clients all point to AdGuard, no DNS leakage.

5. Traffic Paths

Normal Mode

Traffic Path: Side Router Proxy + Dual Path for Domestic Direct Connection Client N100 (.7) · nftables prerouting Hairpin: MASQUERADE source→.7 ① Mainland IP Firewall return Does not enter mihomo, direct connection home ② Remaining Traffic mihomo Split Domestic Direct / International Proxy Main Router .1 PPPoE → Public li-home-0 (.8) Gateway→.1, direct to main router, bypasses side router ① Mainland IPs are returned at the firewall prerouting stage via the china_ip set (~3.7k chnroute). They physically do not enter mihomo, providing hard protection for domestic traffic.

① Mainland IPs are returned at the firewall layer, physically not entering mihomo — Hard protection for domestic traffic. The single-port hairpin (in and out via the same br-lan) forwards back to .1, must MASQUERADE the source to .7, otherwise asymmetric return traffic will cause immediate disconnection. ② fake-ip misses CN domains in geosite (getting 198.18.x) still enter mihomo, relying on the tail rule GEOIP,CN,DIRECT (resolving real IP) as a secondary fallback. li-home-0 uses TUN self-proxying separately, DNS returns real IPs, no fake-ip ambiguity, and domestic connection is inherently stable.

Fail-Open Mode (mihomo/AdGuard Down)

Fail-Open: All Direct Connection When Side Router Fails, Ensuring No Downtime Client N100 (.7) · Still Gateway nft redirect removed · MASQ injected Main Router .1 PPPoE · DNS Takeover Public Trigger: mihomo or AdGuard process abnormal → nikki-failopen (procd) self-heals Behavior: nftables redirect table completely deleted → All traffic goes directly to main router → DNS :53 redirected to main router No Proxy · No Ad Filtering · All Direct — Degraded Fallback, No Downtime

6. Split-Route Strategy & Domestic Traffic Protection

A CN domain missed by geosite once caused MATCH,PROXY → US node reconnecting to domestic server → GFW identified "domestic traffic" and blocked the VPS IP. Now using Dual-Layer Protection:

Layer 1 (Hard, Firewall): bypass_china_mainland_ip=1 (v4) populates the nft china_ip set (~3.7k chnroute). Mainland IPs are returned at prerouting, physically unable to reach mihomo, regardless of whether the domain matches geosite.

  • Single-port hairpin requires table inet bypass_masq to MASQUERADE forwarded traffic source to .7.
  • v6 firewall bypass is currently disabled (to avoid v6 hairpin issues); v6 CN traffic falls back to mihomo GEOIP,CN.

Layer 2 (Fallback, mihomo Rules): Tail rules GEOSITE,cn,DIRECTGEOIP,CN,DIRECTMATCH,PROXY. CN domains missed by fake-ip enter mihomo, where GEOIP,CN resolves the real IP to intercept.

Key sysctl

ParameterValueReason
net.ipv4.conf.*.send_redirects0Otherwise .7 sends ICMP redirects, causing clients to bypass .7 and connect directly to .1, forming a "dual path": same target client direct connection + mihomo dial-up coexist. After NAT through the same public IP on the main router, the server PAWS discards the one with the smaller timestamp → i/o timeout storm (the real culprit for apps like Xianyu freezing). Applies to all single-arm/hairpin side router solutions, not just this one.
net.ipv4.conf.*.rp_filter0Allows asymmetry, works with hairpin

International Lines

Three VPSs are distributed across different network lines. Latency and stability differences stem from the QoS levels of the return lines:

LineFull NamePriorityCharacteristicsSuitable For
CN2 GIAChina Telecom Next Gen CarrierHighestTelecom top-tier line, entire CN2 backbone without passing through 163 network, lowest QoS, basically no speed drop during evening peaksMain nodes requiring low latency/stability
9929China Unicom PremiumMedium-HighUnicom premium line, similar to CN2 GIA but uses Unicom backbone, optimal for Unicom broadband users in the NorthOptimal for Unicom users
4837China Unicom StandardStandardUnicom standard international line, obvious congestion during evening peaks but cheapBackup/Cold backup/Budget-sensitive

Local Nodes

NodeLineProtocolLatencyNotes
US3-Hy2 (Primary)CN2 GIAHysteria2 (UDP+Brutal)136msMain node, stable during evening peaks; anti-packet loss
US2-Hy29929Hysteria2168msUnicom line, fast in the North
US1-Hy24837Hysteria2213msCheap backup, slow during evening peaks
US*/REALITYSameVLESS+REALITY (TCP)420–569msOver GFW, QoS/packet loss amplifies latency, only for extreme backup

Hy2 is UDP+Brutal for anti-packet loss, REALITY is TCP — when bypassing GFW, TCP is dropped by QoS and retransmissions amplify latency, while UDP bypasses directly. Thus, the three REALITY nodes have 3x the latency of Hy2 and are basically decorative. PROXY is a manual Selector pinned to US3-Hy2, not Auto — better to manually switch if the primary fails than let mihomo auto-switch to a slow node (switching makes the user experience "internet is broken but not completely" — much harder to troubleshoot).

VPS Provider Reference

Line quality is important, but VPS providers matter too — for the same CN2 GIA, different vendors have vastly different oversubscription ratios, line quality, and TCP handshake packet loss rates to mainland China. Personal阶段性 research results (prices vary over time, for reference only):

High Quality (Primary Recommended):

VendorLineReference PriceCharacteristics
DMITCN2 GIA~$12–15/mo (1C1G)Old CN2 GIA provider, extremely stable line, almost 0 packet loss to mainland; expensive but worth it
BandwagonHost (Old Dig)CN2 GIA~$50–100/yr (CN2 GIA plan)Old brand, CN2 GIA plan has good cost-performance, DIA line; often out of stock, need to wait
SharonCN2 GIA/9929~$8–12/moNiche but solid lines, 9929 plan has outstanding cost-performance

Cost-Effective (Usable):

VendorLineReference PriceCharacteristics
Lisa Host4837/CN2 Mix~$5–8/moAverage lines (mostly 4837), slow during evening peaks, sufficient for backup lines not sensitive to latency
RackNerdStandard International~$15–30/yrSuper cheap but poor return line, only suitable for cold backup/non-real-time use

Cheap (Backup/Experiment):

VendorLineReference PriceCharacteristics
Yecao Cloud4837~$3–5/moCheap, severe congestion during evening peaks, only suitable as the last fallback
CloudConeStandard International~$20–30/yrUltra-low annual payment, good OOKLA speeds but poor mainland TCP, only for cold backup

Selection Principle: Don't save money on primary nodes — use DMIT or BandwagonHost for CN2 GIA, stability is speed; use 9929 (good for Unicom) or 4837 (cheap) for backups, hang REALITY cold backup on the cheapest one. Local US3 is on DMIT CN2 GIA, US2 on 9929, US1 on 4837 cheap machine.

Speedup Items

  • tcp-concurrent: true: Concurrent dial-up resolves multiple IPs and picks the fastest.
  • QUIC reject rule intentionally kept: AND,(DST-PORT,443),(NETWORK,UDP),(NOT,GEOSITE,cn),REJECT forces external HTTP/3→h2, curing "QUIC-over-Hy2 idle reuse hang". Opening it will cause recurrence.

Why not hard bypass for IPv6: The key to v4 hard bypass is hairpin MASQUERADE (changing source to .7 to ensure symmetric return). This operation relies on N100 having a fixed internal v4 address. However, v6 addresses are dynamically assigned via SLAAC, with no stable internal address to act as an SNAT anchor — hairpin return symmetry cannot be reliably achieved on v6. Forcing it on will only cause random disconnections. Therefore, v6 CN currently only relies on mihomo rule layer GEOIP,CN fallback, not firewall prerouting hard bypass. Monitoring: Key components of N100, li-home-0, and main router are integrated into Prometheus + Grafana, see monitoring.md. Node availability monitoring relies on Prometheus Blackbox Exporter probes, but switching is still manual (not using Auto is an intentional choice — better to manually switch if the primary fails than auto-switch to a slow node).

7. N100 Key Components

ComponentAddress/PathDescription
mihomo Kernel/usr/bin/mihomo, procd via /etc/init.d/nikkiTransparent Proxy + TUN
mihomo API127.0.0.1:9090Health Probe
mihomo Mixed:7890 (SOCKS5+HTTP)Explicit Proxy Entry
mihomo Redir:7891Transparent Proxy Redirect Inbound
mihomo DNS:1053 (fake-ip)AdGuard Upstream
Nikki Dashboardhttp://.7:9090/uiWeb Console
AdGuard HomeDocker host network, http://.7:8083DNS Ad Filtering, upstream→:1053
failopen Daemon/usr/local/sbin/nikki-failopen.sh, procdHealth Check + Auto Fail-Open
CN Bypass SNAT/etc/nikki/scripts/bypass-masq.{nft,sh} + firewall includeHairpin masquerade, persistent

N100 Migration Motivation & Hardware Selection: Previously, the side router ran on the desktop via WiFi, and WiFi jitter caused whole-house disconnection. Switched to N100 (low-power x86, iStoreOS, single Gigabit port) hanging on the Study AP LAN port as a wired side router, decoupling from WiFi.

8. nftables Structure

Processing Order (prerouting → forward → postrouting):

nftables Processing Order: prerouting → forward → postrouting Inbound Packet fw4 forward (policy drop) nikki dstnat / mangle_prerouting Transparent Proxy Entry · Split by Destination Mainland IP / Private → return, bypass proxy (direct) Remaining Traffic → DNAT :7891 or Mark TUN bypass_masq postrouting Hairpin SNAT Anchor CN Direct Traffic → MASQUERADE (src→.7, symmetric return) fw4 srcnat_lan (docker MASQUERADE) Outbound Mainland IPs are returned at prerouting, physically not entering mihomo, regardless of whether the domain matches geosite. bypass_masq is an independent table; nikki restart/fail-open does not affect the symmetric SNAT anchor for return traffic.

table inet fw4 (OpenWrt Firewall)

  • forward: policy drop; allow TUN traffic + LAN→WAN forwarding
  • srcnat_lan: docker MASQUERADE

table inet nikki (Transparent Proxy Rules)

  • china_ip set: ~3.7k chnroute CIDRs (populated by bypass_china_mainland_ip=1)
  • dstnat / mangle_prerouting_lan: Mainland IP / Private / 198.18 match → return bypass; others DNAT TCP→:7891 or Mark TUN
  • mangle_output: Local outbound → TUN
  • Entire table deleted on fail-open

table inet bypass_masq (CN-IP Bypass Hairpin SNAT)

  • postrouting (nat hook, priority srcnat+5): iifname br-lan oifname br-lan ip daddr != {private+198.18+multicast} masquerade
  • Only hits public CN direct traffic bypassed by china_ip that needs forwarding back to .1, changing source to .7 to ensure symmetric return
  • Independent of table nikki, nikki restart does not affect; persistence relies on firewall.bypass_masq include

table inet failopen (Failure Injection, Deleted on Recovery)

  • srcnat: MASQUERADE 192.168.31.0/24 → non-local
  • dns: DNAT :53 → .1:53

9. External Services (li-home-0)

All these services rely on li-home-0's WiFi. wifi-watchdog (systemd timer 30s) automatically recovers from RF/driver anomalies — brief jitter can self-heal. Long-term high-availability services should be migrated to the wired N100.

ServicePurposeAvailability
DERPTailscale RelayHigh
ratholeIntranet Penetration (see rathole-tunnel.md)High
paste/mdserveNote SharingMedium
PrometheusMonitoring CollectionMedium
sunshineGame StreamingLow
Local LLMAI Inference (see local-llm.md)Low

10. Daemons

DaemonLocationScenario
nikki-failopenN100 (procd)mihomo/AdGuard process abnormal
Power-on Self-StartN100 (BIOS)Power outage recovery
wifi-watchdogli-home-0 (systemd timer)WiFi driver/RF abnormal
sync-mihomo-to-routerli-home-0 (systemd timer, 03:45)Source→Side Router Config Drift
sync-subscribeli-home-0 (systemd timer, 03:30)Source→Public Subscription Config Drift

Config Derivation Pipeline

There are three places in the house needing mihomo config: Local Machine (TUN self-proxy), N100 (Whole-house transparent proxy), Public Subscription (Mobile roaming import). The node lists, proxy groups, and split rules must be consistent across all three, but DNS, TUN, and inbound methods differ. Maintaining them independently would inevitably lead to drift.

Solution: Single source of truth (/opt/mihomo/mihomo_config/config.yaml, the only manually edited file, containing proxies/groups/rules sections) + two timer-driven derivation scripts.

#1 Local Machine: Directly uses the source. TUN device Meta + Local AdGuard + fake-ip + controller.

#2 N100 Side Router (sync-mihomo-to-router.sh, timer 03:45):

  • Extracts only proxies/proxy-groups/rules sections from source → /etc/nikki/profiles/home.yaml
  • Local machine mihomo -t validates source → scp to N100 → nikki -t validates → Backup old file (with timestamp) → Auto-rollback on validation failure → Reload Nikki
  • DNS/TUN/sniffer are injected by N100 local UCI mixin, not entering the profile — these are target-end specific and should not be overwritten from the source

#3 Public Subscription (sync-subscribe.sh, timer 03:30):

  • Outputs clash.yaml (Clash import) + sub.txt (v2rayN base64 share link)
  • Local-machine sanitization: Remove external-controller+secret (leak leads to control), tun entire section (phones don't need it), nameserver:127.0.0.1 (roaming devices can't reach), PROCESS-NAME rules (phones never match)
  • DNS replaced with fake-ip + Public DoH, international domains resolved via exit node to prevent leakage
  • Entry: https://subscribe.liz6.com (Caddy file_server, path token auth)

The two timers are staggered by 15 minutes to avoid simultaneous writes to N100. If you modify the source and want immediate effect, manually run the corresponding script; scripts are idempotent.

11. Known Risks

RiskImpactMitigation
li-home-0 WiFi Single PointHigh-availability services like rathole/DERP all go downLong-term migration to wired N100; wifi-watchdog 30s self-heal is short-term bleeding stop
N100 Single Port HairpinSingle-arm topology asymmetric return must disconnectMASQUERADE + bypass_masq nft table ensures symmetric return
mihomo/AdGuard Process DownWhole-house disconnection (DHCP gateway still points to .7)nikki-failopen (procd) process-level self-heal
Main Router Hardware/Firmware FailureWhole-house disconnection (PPPoE+NAT+DHCP all down)No hot backup planned; original plan for N100 as main router not implemented
Optical Modem BridgedONT only does optical-electrical conversion, no dial-up/routing/NAT, public IP directly to main routerBasically not a failure source, no double NAT

Detailed assessment and recovery procedures see network-recovery.md.

Change Log

DateChange
2026-06-21N100 side router online, li-home-0 side router retired; DHCP gateway/DNS switched from .8 to .7; N100 fail-open + li-home-0 wifi-watchdog deployed
2026-06-24Public subscription fix: Original direct copy of local config caused leakage of local DNS + management port secret + tun; sync-subscribe.sh added local-machine sanitization; Side router profile re-run refreshed; Added sync-mihomo-to-router.timer
2026-06-27Disabled ICMP send_redirects: Cured Xianyu app freezing (root cause of dual-path PAWS packet loss storm); CN-IP firewall bypass online (bypass_china_mainland_ip=1 + table inet bypass_masq); tcp-concurrent + CN DNS changed to plaintext UDP 223.5.5.5; Node insights