Introduction
Connect your servers, sites, and devices into one private, encrypted network — shipped as a single tiny binary that runs anywhere, from a cloud VM to a MikroTik router.
This documentation site is bilingual. Use the 中文 / EN toggle in the top bar to switch languages, or read the 简体中文文档.
What is Subnetra?
Subnetra stitches machines in different places — offices, a data center, roaming laptops, home labs, containers, routers — into a single flat private subnet.
It uses a hub-and-spoke design: a reachable hub relays traffic between spokes, so any node can reach any other by a stable overlay IP even when most of them sit behind NAT. Every packet travels fully encrypted over an ordinary UDP tunnel, and the whole thing is one self-contained binary — nothing else to install, no kernel modules, no daemon zoo.
What you can do with it
- 🏢 Link branch offices to a data center — route whole subnets site-to-site over one private overlay.
- 💻 Give roaming laptops a stable private IP that follows them across Wi-Fi, LTE, and home networks.
- 🧪 Reach home-lab / IoT / container services as if they were on the same LAN.
- 🛰 Publish a LAN from behind NAT — e.g. a MikroTik router exposing
192.168.88.0/24to the mesh. - 📦 Run where heavy VPN stacks won’t fit — constrained containers, BusyBox, small ARM boxes, edge routers.
Highlights
- Runs anywhere, installs as one file — a single static binary (under 512 KB on
Linux) with zero external dependencies. Drops onto cloud VMs, containers, BusyBox,
Raspberry Pi, and MikroTik RouterOS. Builds for
amd64/arm64/armv7/armv5, plus a native macOS spoke. - Encrypted by default, quiet on the wire — every packet is ChaCha20-Poly1305
encrypted with per-link keys and replay protection. There are no magic bytes, and
unauthenticated packets are dropped silently — to a port scanner the tunnel looks like
nothing is listening. Header obfuscation is on by default (
obfuscate, mesh-wide): the 20-byte framing header is XOR-masked per packet so the whole datagram looks random and the NAT keepalive cadence is de-periodized — no protocol fingerprint for a passive observer. Set"obfuscate": falsefor a readable cleartext header (e.g. packet-capture debugging). Obfuscation hides the protocol fingerprint, not packet length or timing. See Wire Protocol → Header obfuscation. - A flat private subnet with policy routing — give every node an overlay IP, route whole subnets site-to-site, and let the hub relay spoke-to-spoke so nodes behind NAT still reach each other.
- Just works behind NAT — spokes keep their own NAT pinhole open with a built-in keepalive, and the hub automatically relearns a spoke that roams to a new address — no external pinger, no manual reconnect.
- Change routes live — inject or update forwarding rules at runtime with zero downtime: no restart, no dropped packets.
- Built to be operated — human-readable and JSON status, per-reason drop counters
that tell you why traffic isn’t flowing, per-peer health/
onlineflags, and a Prometheus exporter for alerting. - Simple, declarative config — describe a node as a
hubor aspokeand the forwarding table is derived for you. Name your peers sostatusreadsbj-office-gw, notid=2.
Quick start
The fastest path is the container image (the hub is typically a public cloud host):
# 1. Create a config — one hub, one or more spokes.
cp config.example.json config.json
# Set a UNIQUE 64-hex key on every peer link: openssl rand -hex 32
# 2. Run it (the tunnel needs the TUN device + NET_ADMIN).
docker run -d --name subnetra \
--cap-add=NET_ADMIN --device=/dev/net/tun \
-v "$PWD/config.json":/etc/subnetra/config.json:ro \
ghcr.io/jamiesun/subnetra:latest
# 3. Check it.
docker exec subnetra subnetra status
Prefer a bare binary? Grab the static build for your architecture from the latest release, then follow the Installation and Quick Start guides. A full hub + two-spoke production walkthrough lives in Production Deployment.
Operate & observe
subnetra status turns the silent, by-design packet drops into countable signals so
you can tell why traffic is or isn’t flowing:
subnetra v0.6.0 [running]
mode=raw_direct local_id=1 udp_port=18020 tun=snr0 peers=2
peers:
id=2 name=bj-office-gw endpoint=203.0.113.2:18020 allowed_src=10.0.0.2/32
id=3 name=alice-laptop endpoint=203.0.113.3:18020 allowed_src=10.0.0.3/32
traffic: tun_rx / udp_tx / udp_rx / tun_tx / relay / keepalive …
drops: unknown_peer / auth_or_invalid / spoof / no_route …
subnetra status --json emits the same data as a stable, versioned JSON object — with a
per-peer last_seen_age_seconds and online flag — and a drop-in Prometheus exporter
turns it into scrapeable metrics. See
Observability & Troubleshooting for the full status
schema, the drop taxonomy, and alerting examples.
How to read these docs
- New here? Start with Installation and the Quick Start.
- Want to understand the design? Read Architecture and the Security Model.
- Setting up a config? See the Configuration Reference and Roles.
- Going to production? Follow Production Deployment, Containers, RouterOS Spoke, or the macOS Spoke runbook.
- Building another implementation? The Wire Protocol is the normative contract.
Project status
The framework, the pure-algorithm layer, and the syscall data path (TUN, the
readiness reactor, AF_UNIX control plane, daemon main loop) are implemented and
exercised end-to-end in the dev container, with a native macOS utun/poll(2)
spoke behind the comptime src/os/ backend. v1 (raw_direct + PSK + anti-replay
- RCU policy) is the deliverable; v2 reliability modes (
kcp_arq,fec_xor) are reserved interface points only — see the Roadmap.
License
MIT © 2026 jettwang.