stunmesh-go
STUNMESH is a WireGuard helper tool that establishes peer-to-peer connections through NAT — without any self-hosted coordination infrastructure.
It discovers each node's public IP and port via STUN, encrypts the endpoint with a Curve25519 sealed box, and shares it through a pluggable storage backend (Cloudflare DNS, OpenDHT, or your own script). Every node reads its peers' endpoints from the same storage and configures WireGuard accordingly. No rendezvous server, no relay, no VPS in the middle.
Inspired by manuels' wireguard-p2p project.
📖 Full documentation: docs.stunmesh.dev
Talks & Presentations
NAT Type Support
- ✅ Full Cone NAT, Restricted Cone NAT, Port Restricted Cone NAT: fully supported
- ⚠️ Symmetric NAT: may be difficult to support due to unpredictable port mapping
For best results, ensure at least one peer is behind a cone NAT type.
- Linux (amd64, arm, arm64, mipsle) - Normal and UPX-compressed binaries
- macOS (amd64, arm64) - Normal binaries only
- FreeBSD (amd64, arm64) - Normal binaries only, requires
wireguard-tools
[!IMPORTANT]
FreeBSD binaries use the wgcli backend, which invokes wg(8). The base system ships the
if_wg kernel module but not that tool, so pkg install wireguard-tools is required.
See Backend Selection.
[!NOTE]
We only support wireguard-go in MacOS, Wireguard App store version is not supported because of sandbox currently.
Quick Start
Download a binary from the releases page, or use the container image:
docker pull tjjh89017/stunmesh
stunmesh-go needs raw socket access, so run it as root next to an already-configured WireGuard interface:
sudo ./stunmesh-go
Configuration is read from /etc/stunmesh/config.yaml, ~/.stunmesh/config.yaml, or ./config.yaml. A minimal two-node setup with the built-in Cloudflare plugin:
---
refresh_interval: "1m"
log:
level: "info"
interfaces:
wg0:
peers:
"PEER_B":
public_key: "<PEER_B_PUBLIC_KEY_BASE64>"
plugin: cf
stun:
address: "stun.l.google.com:19302"
plugins:
cf:
type: builtin
name: cloudflare
zone: example.com
token: ${CLOUDFLARE_API_TOKEN}
subdomain: wg
Run the same setup on the other node (with this node's public key), wait roughly two refresh intervals, and the tunnel comes up. Verify with wg show or by pinging the peer's tunnel address.
[!IMPORTANT]
stunmesh-go reads the WireGuard device's state once at startup — restart it after the
interface is recreated, the listen port or fwmark changes, or config.yaml is edited.
Under systemd, bind it to the WireGuard unit (After= + BindsTo=) so this is enforced
automatically. See when stunmesh-go must be restarted.
Documentation
Build
make all
[!NOTE]
For FreeBSD and MacOS, please use GNU Makefile gmake to build.
Build options (built-in plugin selection, binary minimization, WireGuard backend) are documented at docs.stunmesh.dev/reference/build. Contrib plugins live in contrib/ and are built with make plugin.
Future work / Roadmap
- one shot command
- auto execute when routing engine notify change
License
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.