Networking Topology
Home Network — Topology & Service Architecture
Unified
192.168.31.0/24subnet across all rooms via Primary Gateway + AP Bridge + L2 backhaul. Serves NixOS development, ROS robotics research, centralized NAS storage, and 20+ homelab web services.
Device Inventory
| Hostname | Role / OS | LAN IP | Tailscale IP | Link |
|---|---|---|---|---|
| Xiaomi Router | Primary gateway (PPPoE / DHCP) | 192.168.31.1 | — | — |
| nas-nasa | QNAP NAS (QTS) — Jellyfin, qBittorrent, WebDAV, Video Station | 192.168.31.12 | 100.64.243.70 | Wired — Living Room |
| id3-eniac | Workstation (NixOS) | 192.168.31.11 | 100.119.161.58 | Wired — Study A |
| lab-matrix | Compute node (NixOS) | 192.168.31.10 | 100.88.42.65 | Wired — Study A |
| ros-rolling | Robotics lab (Arch) | 192.168.31.91 | 100.117.23.98 | Wired — Study A |
| wsl-wine | Dev environment (Win) | 192.168.31.33 | — | Wired — Study A |
| ASUS Router | Access Point (bridge mode) | 192.168.31.31 | — | Wired — MDF |
| id3-yoga | Laptop (NixOS) | 192.168.31.22 | 100.72.112.107 | WiFi — Study A |
| sbc-opi5p | ARM node (NixOS) | 192.168.31.209 | 100.81.163.123 | WiFi — Study A |
| MI TV | Smart TV (Android) | 192.168.31.13 | — | WiFi — Living Room |
| Mi phone | Mobile (Android) | 192.168.31.14 | 100.84.127.17 | WiFi — Living Room |
| vps-pacman | Edge VPS (NixOS) | — | 100.88.42.65† | Vultr VPS (Tokyo) |
† vps-pacman peers with lab-matrix; public IP not listed.
Physical Wiring
MDF (弱电箱 — Distribution Box)
Central switching hub for structured cabling:
- ONT (Modem): Fiber entry point, bridged to Living Room Port A via in-wall CAT6.
- L2 Gigabit Switch: Interconnects LAN backhaul from Living Room to Study A and Bedroom.
Living Room — Primary Hub
- Xiaomi Router: PPPoE session termination (WAN from ONT). LAN backhaul to MDF switch.
nas-nasaconnected directly to router LAN port. - Wireless clients: MI TV, Mi phone.
Study A — Workstation Cluster
- ASUS Router (AP Mode): Transparent bridge to
192.168.31.0/24. Serves wired cluster (id3-eniac,wsl-wine,ros-rolling,lab-matrix) and WiFi for portable NixOS nodes (id3-yoga,sbc-opi5p).
Topology Diagram
graph TB
%% ════════════════════════════════════
%% Layer 0 — External
%% ════════════════════════════════════
Internet((🌐<br/>Internet))
CF[☁️ Cloudflare DNS<br/>alienzj.org]
%% ════════════════════════════════════
%% Layer 1 — Edge VPS
%% ════════════════════════════════════
subgraph L1["🛡️ L1 — Edge VPS (Vultr Tokyo)"]
NginxL1[🔒 Nginx QUIC<br/>SSL *.alienzj.org]
ACME[📜 ACME<br/>DNS-01]
OA2_L1[🔑 OAuth2-Proxy<br/>auth.alienzj.org]
SB[🔄 Sing-box<br/>egress]
end
%% ════════════════════════════════════
%% Layer 2 — Tailscale Overlay
%% ════════════════════════════════════
TS{{🔗 Tailscale Mesh<br/>WireGuard · MagicDNS<br/>100.64.0.0/10}}
%% ════════════════════════════════════
%% Layer 3 — Home Physical LAN
%% ════════════════════════════════════
subgraph Home["🏠 Home LAN — 192.168.31.0/24"]
Fiber[⚡ Fiber<br/>ONT]
Xiaomi{Xiaomi Router<br/>.1}
Switch[🔌 L2 Switch<br/>MDF]
ASUS((ASUS AP<br/>.31))
subgraph NAS_Box["🗄️ NAS — nas-nasa (.12)"]
direction LR
NAS_Stor[💾 Storage<br/>NFS / SMB / WebDAV]
Jellyfin[🎬 Jellyfin<br/>media server]
QB[📥 qBittorrent]
VidStation[📼 Video Station]
end
TV[📺 MI TV<br/>.13]
end
%% ════════════════════════════════════
%% Layer 4 — L2 Homelab Nodes
%% ════════════════════════════════════
subgraph L2["⚙️ L2 — Homelab Nodes"]
subgraph Matrix["🖥️ lab-matrix (.10)"]
direction LR
NginxL2[🌐 Nginx<br/>internal]
OA2_L2[🔑 OAuth2<br/>local]
Kanidm[👤 Kanidm<br/>id.alienzj.org]
PG[🐘 PG 18]
Services[📦 22 Services<br/>git · bw · immich · mail<br/>paper · rss · atuin · books<br/>affine · links · pdf · office<br/>note · meal · grocy · dav<br/>anki · push · status · metrics<br/>glance]
end
subgraph Eniac["🖥️ id3-eniac (.11)"]
Ollama[🤖 Ollama]
Jupyter[📓 JupyterHub]
end
subgraph ROSBox["🤖 ros-rolling (.91)"]
ROS[⚙️ ROS 2<br/>Arch Linux]
end
subgraph WSLBox["🪟 wsl-wine (.33)"]
WSL[🍷 Wine / Dev<br/>Windows + WSL]
end
subgraph OPIBox["🔌 sbc-opi5p (.209)"]
OPI[⚡ ARM Edge<br/>RK3588]
end
end
%% ════════════════════════════════════
%% Tailscale Clients (mobile)
%% ════════════════════════════════════
Phone[📱 Phone<br/>TS .17]
Laptop[💻 Laptop<br/>TS .107]
%% ── Path A: Public Internet ──
Internet -->|"HTTPS"| CF
CF -->|"TLS 1.3"| NginxL1
NginxL1 --> ACME
NginxL1 --> OA2_L1
NginxL1 --> SB
NginxL1 <==>|"HTTP/3 QUIC<br/>over WireGuard"| TS
%% ── Path B: Tailscale Direct ──
Phone -.->|"WireGuard"| TS
Laptop -.->|"WireGuard"| TS
%% ── L1 → L2 via Tailscale ──
TS <==>|"proxy to L2"| NginxL2
NginxL2 --> OA2_L2
OA2_L2 -->|"OIDC"| Kanidm
NginxL2 --> Services
Services --> PG
%% ── Physical LAN ──
Fiber ==>|"PPPoE"| Xiaomi
Xiaomi --> NAS_Box
Xiaomi -. "WiFi" .-> TV
Xiaomi -. "WiFi" .-> Phone
Xiaomi -->|"LAN Backhaul"| Switch
Switch --> ASUS
Switch --- Bedroom[🛏️ Bedroom<br/>Wall Jack]
ASUS --> Matrix
ASUS --> Eniac
ASUS --> ROSBox
ASUS --> WSLBox
ASUS -. "WiFi" .-> Laptop
ASUS -. "WiFi" .-> OPIBox
%% ── NAS Services ──
Eniac -->|"📦 NFS/SMB mounts"| NAS_Stor
Eniac -->|"🔄 restic backup"| NAS_Stor
Eniac -->|"🎬 stream"| Jellyfin
TV -->|"🎬 stream"| Jellyfin
TV -->|"📼 stream"| VidStation
%% ── Tailscale Peers ──
NAS_Box -.-> TS
Matrix --> TS
Eniac -.-> TS
OPIBox -.-> TS
%% ── Styling ──
style L1 fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
style L2 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
style Home fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style TS fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style Matrix fill:#c8e6c9,stroke:#43a047
style Eniac fill:#c8e6c9,stroke:#43a047
style OPIBox fill:#c8e6c9,stroke:#43a047
style Xiaomi fill:#f96,stroke:#333,stroke-width:2px
style ASUS fill:#69f,stroke:#333
style Switch fill:#dfd,stroke:#333
style Fiber fill:#eee,stroke:#333
style Services fill:#fff9c4,stroke:#f9a825
style Kanidm fill:#ffccbc,stroke:#e64a19
style PG fill:#bdbdbd,stroke:#616161
style NginxL1 fill:#b3e5fc,stroke:#0277bd
style NginxL2 fill:#b3e5fc,stroke:#0277bd
style NAS_Box fill:#e1bee7,stroke:#8e24aa
style NAS_Stor fill:#ce93d8,stroke:#7b1fa2
style Jellyfin fill:#ce93d8,stroke:#7b1fa2
style QB fill:#ce93d8,stroke:#7b1fa2
style VidStation fill:#ce93d8,stroke:#7b1fa2
NAS Integration — nas-nasa
nas-nasa (QNAP TS-453D, QTS) is the central storage server, wired directly to the Xiaomi router in the Living Room.
Services running on the NAS:
- Jellyfin — media server, serves video/audio to id3-eniac and MI TV
- qBittorrent — download client
- WebDAV — file access for remote clients
- Video Station — QNAP native media streaming to MI TV
id3-eniac ↔ NAS integration:
- NFS/SMB mounts — id3-eniac mounts multiple NAS shares as local directories
(
/mnt/nasa/media,/mnt/nasa/backup, etc.) - restic backup — id3-eniac runs scheduled restic backups to NAS storage
(declared via
modules.services.sharing.restic)
Media access:
- id3-eniac → Jellyfin server on NAS for browsing library + playback
- MI TV → Jellyfin (native Android TV app) + Video Station for direct streaming over WiFi via the Xiaomi router
### How Services Are Accessed
| Aspect | Path A — Public | Path B — Tailscale Direct |
|---|---|---|
| **DNS** | Cloudflare → vps-pacman public IP | Tailscale MagicDNS → lab-matrix Tailscale IP |
| **SSL termination** | vps-pacman (Let's Encrypt wildcard `*.alienzj.org`) | None (WireGuard encrypts) |
| **SSO check** | vps-pacman OAuth2-Proxy → L2 re-checks via local oauth2-proxy | lab-matrix OAuth2-Proxy (local) |
| **Latency** | ~50-100ms (Cloudflare → VPS → Tailscale → homelab) | ~1-5ms (direct WireGuard mesh) |
| **Availability** | Requires vps-pacman + lab-matrix both up | Requires only lab-matrix up |
### SSL: Why Only L1 Runs Certificates
SSL terminates at vps-pacman (L1). lab-matrix (L2) runs all services with
`nginx.ssl = false`. This is safe because:
| Link | Transport | Encryption |
|---|---|---|
| Browser → vps-pacman | Public internet | **HTTPS** (Let's Encrypt `*.alienzj.org` wildcard) |
| vps-pacman → lab-matrix | Tailscale mesh | **WireGuard** (ChaCha20-Poly1305) |
| nginx → oauth2-proxy | Unix socket | Filesystem permissions only |
| oauth2-proxy → Kanidm | Loopback TCP | No network exposure |
For full details see [sso-identity.md](sso-identity.md#ssl-termination-where-and-why)
and [web-services.md](web-services.md#ssl-and-acme-host-level-constraint).
### L1 → L2 Proxy Map
The L1 gateway (vps-pacman) auto-generates nginx vhosts from a declarative map.
Adding a service on any L2 host is a single line:
```nix
# hosts/vps-pacman/modules/nginx.nix
homelabServices = {
id = "lab-matrix"; # Kanidm identity provider
git = "lab-matrix"; # Forgejo
bw = "lab-matrix"; # Vaultwarden
immich = "lab-matrix"; # Immich
mail = "lab-matrix"; # Stalwart
paper = "lab-matrix"; # Paperless-ngx
rss = "lab-matrix"; # FreshRSS
atuin = "lab-matrix"; # Atuin shell history
books = "lab-matrix"; # Calibre
affine = "lab-matrix"; # Affine
links = "lab-matrix"; # Linkwarden
pdf = "lab-matrix"; # Stirling-PDF
office = "lab-matrix"; # OnlyOffice
note = "lab-matrix"; # HedgeDoc
meal = "lab-matrix"; # Mealie
grocy = "lab-matrix"; # Grocy
dav = "lab-matrix"; # WebDAV (Dufs)
anki = "lab-matrix"; # Anki Sync
push = "lab-matrix"; # Gotify
status = "lab-matrix"; # Gatus
metrics = "lab-matrix"; # VictoriaMetrics
glance = "lab-matrix"; # Glance dashboard
ollama = "id3-eniac"; # Ollama (GPU host)
jupyter = "id3-eniac"; # JupyterHub (GPU host)
};
Each entry generates https://<key>.alienzj.org → reverse proxy to the named
host over Tailscale MagicDNS. Adding a new service anywhere in the tailnet is a
one-line entry in this map.
Tailscale Subnet Routing
Devices without Tailscale clients (MI TV, Xiaomi admin UI) are reachable via subnet routing. Configure a wired node to advertise the LAN:
tailscale up --advertise-routes=192.168.31.0/24
Approve the route in the Tailscale/Headscale admin console. All peers can then reach the full home subnet over the mesh.
ISP CGNAT and the 100.64.0.0/10 Address Range
You may notice 100.64.x.x addresses in two completely different places: on
your Xiaomi router’s WAN status page (e.g. 100.64.73.53 with gateway
100.64.0.1), and in tailscale status as your nodes’ mesh IPs (e.g.
100.64.243.70). These are independent systems that happen to share the
same address space.
The Range: RFC 6598 Shared Address Space
100.64.0.0/10 is defined by RFC 6598 (IANA Shared Address Space) — it is
reserved for Carrier-Grade NAT (CGNAT), not for private LANs. Critically:
- It is NOT RFC 1918 private (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) - Python’s
ipaddress.IPv4Address("100.64.243.70").is_privatereturnsFalse - Python’s
ipaddress.IPv4Address("100.64.243.70").is_globalreturnsTrue
This has real consequences for applications that throttle bandwidth based on whether an IP is “local” vs “remote” — see networking-vpn.md § Jellyfin/LAN Streaming.
ISP Side: Carrier-Grade NAT on the WAN
Internet
└─ ISP CGNAT gateway: 100.64.0.1 ← ISP upstream (RFC 6598)
└─ Router WAN IP: 100.64.73.53 ← ISP-assigned CGNAT address
└─ Router NAT: 192.168.31.0/24 ← YOUR home LAN (RFC 1918)
├─ id3-eniac: .11
├─ nas-nasa: .12
├─ id3-yoga: .22
└─ ros-rolling: .91
Chinese ISPs widely deploy CGNAT because they lack enough public IPv4 addresses.
Your router receives a 100.64.x.x address from the ISP’s CGNAT pool and
performs a second layer of NAT to your 192.168.31.0/24 LAN. The address
100.64.0.1 on the router’s status page is the ISP’s CGNAT gateway — it has
nothing to do with Tailscale.
The double NAT (ISP CGNAT + home router) can degrade peer-to-peer connectivity and is one reason Tailscale may fall back to DERP relay for nodes behind particularly restrictive CGNAT implementations.
Tailscale Side: Overlay Mesh
Tailscale mesh (WireGuard)
├─ nas-nasa: 100.64.243.70
├─ id3-eniac: 100.119.161.58
├─ id3-yoga: 100.72.112.107
├─ lab-matrix: 100.88.42.65
├─ ros-rolling: 100.117.23.98
├─ sbc-opi5p: 100.81.163.123
├─ vps-pacman: 100.87.3.73
└─ vps-ultraman: 100.67.58.96
Tailscale also uses 100.64.0.0/10 for its mesh IPs because RFC 6598 addresses
are guaranteed not to conflict with public internet routes. Tailscale’s use is
completely independent of the ISP’s CGNAT — two separate systems, same RFC.
The Coincidence
ISP CGNAT and Tailscale both chose 100.64.0.0/10 because it is the only large
RFC-defined address block explicitly intended for non-public, non-private use.
This is by design — the IETF created this range exactly so ISPs could do CGNAT
without conflicting with RFC 1918 home/office networks or public internet
routes. Tailscale’s choice to use the same range is deliberate for the same
reason: no conflict with your LAN, no conflict with the internet.
Practical Implication: Application IP Classification
Because RFC 6598 addresses are not RFC 1918 private, many applications
(especially Python-based ones) classify 100.64.x.x as “remote” or “internet”:
| App | What breaks | Symptom |
|---|---|---|
| jellyfin-mpv-shim | remote_kbps: 10000 (10 Mbps) applied to Tailscale IP | ~1.25 MB/s streaming instead of full LAN |
| Syncthing, Resilio | Relay-only mode forced for “non-LAN” peers | Slow sync over DERP instead of direct |
Any Python app using ipaddress.is_private | Thinks your local NAS is across the internet | Bandwidth throttles, connection limits |
Rule: For any local service on the 192.168.31.0/24 LAN, always configure
clients to use the LAN IP (192.168.31.x), not the Tailscale IP
(100.64.x.x). Reserve Tailscale IPs for remote access scenarios where the LAN
is unreachable.
Optimization Notes
2.5G internal backbone: The current 1G switch limits internal transfers (Nix store copies, NAS backups) to ~115 MB/s. Upgrading the MDF switch and ASUS AP to 2.5G-capable hardware doubles NixOS remote build throughput between wired nodes.
WiFi channel separation: Two APs in proximity risk co-channel interference. Assign Xiaomi and ASUS to non-overlapping 5 GHz channels (e.g. Channel 36 and Channel 149).
Fleet orchestration: Four NixOS nodes are managed via the shared flake in this repo. See ops.md for deployment commands and web-services.md for the full homelab services documentation.
Network Speed & Topology Verification
In June 2026, network speed and path audits between the workstation (id3-eniac) and the NAS (nas-nasa) were conducted to document throughput performance and explain anomalies in the hardware reporting:
1. Throughput & Interface Capabilities
- QNAP NAS Ports: Both
eth0andeth1interfaces on nas-nasa are running on native 2.5G ports and negotiate at 2.5 Gbps (2500 Mb/s). - Direct LAN SMB Speed: Copying files directly over the SMB mount (
192.168.31.12) achieves 117 MB/s (fully saturating the Gigabit switch backbone). - Rsync Upload Speed: Direct file transfers over SSH/LAN from the workstation to the QNAP home directory achieve 111.17 MB/s.
- Internet Downloads: Workstation internet downloads achieve up to 100 MB/s (~800 Mbps), and QNAP qBittorrent downloads achieve up to 80 MB/s (~640 Mbps).
2. Physical Cabling Validation (WAN vs. Backhaul)
The Xiaomi Router in the Living Room connects back to the MDF switch and Modem via two separate physical Ethernet cables:
- WAN path: Modem (ONT) in the MDF box is bridged to the Xiaomi Router’s WAN port using one cable.
- LAN backhaul path: Xiaomi Router’s LAN port returns to the MDF switch using a second cable. Because two separate cables are used (rather than a single split 4-wire line), both links negotiate at 1 Gbps, enabling Gigabit speeds for the Study A workstation cluster.
3. ASUS AP WAN Port UI Behavior
On the ASUS AP (running in bridge mode), the uplink cable from the MDF switch is connected to the physical WAN port.
- The Symptom: The ASUS router’s web interface alternates between displaying the Xiaomi Router and the QNAP NAS as the device connected to the WAN port.
- The Cause: In AP mode, all switch ports (WAN + LAN 1-4) are bridged together into a single broadcast domain (
vlan1). The WAN port receives packets from all devices upstream of the AP. Because the ASUS firmware’s client tracking daemon is designed to expect only a single parent device on the WAN port, seeing multiple active MAC addresses (Xiaomi Router and QNAP NAS) confuses the web UI, causing it to alternate between showing them. This is a purely cosmetic firmware display bug and has no impact on network performance.