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
- FOSDEM 2026 - STUNMESH-go: Building P2P WireGuard Mesh Without Self-Hosted Infrastructure
- Slides
- Recording Video
- ✅ 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)
- macOS (amd64, arm64)
- FreeBSD (amd64, arm64) - requires
wireguard-tools - Windows (amd64, arm64) - shipped as
stunmesh-windows-<arch>-<tag>.zip, requires the official WireGuard for Windows client - Android - separate app, see stunmesh-android
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.
Download a binary from the releases page, or use the container image:
docker pull ghcr.io/tjjh89017/stunmeshWarning
The Docker Hub image (tjjh89017/stunmesh) is deprecated and will stop
receiving updates in a future release. Pull from ghcr.io instead.
On Linux, macOS, and FreeBSD, stunmesh-go needs raw socket access, so run it as root next to an already-configured WireGuard interface (Windows differs — see Windows below):
sudo ./stunmesh-goOn Linux, instead of running as root you can grant just the two capabilities it needs:
setcap cap_net_admin,cap_net_raw+ep ./stunmesh-goIt runs as a daemon by default; pass -oneshot to publish and establish 3 times and then exit.
Configuration is read from /etc/stunmesh/config.yaml, ~/.stunmesh/config.yaml, or ./config.yaml (.yml also works), or pass a file directly with -c <file>. 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:
addresses: ["stun.l.google.com:19302"]
plugins:
cf:
type: builtin
name: cloudflare
zone: example.com
token: "<CLOUDFLARE_API_TOKEN>"
subdomain: wgRun 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.
Windows has no equivalent of the raw sockets (Linux) or pcap (macOS/BSD) stunmesh-go uses to share WireGuard's UDP port, so it instead runs a local UDP proxy that owns the public-facing socket, performs STUN on it, and relays WireGuard packets to per-peer loopback listeners. The official WireGuard for Windows client stays the data plane, and stunmesh-go rewrites each peer's endpoint to its loopback listener itself — there is nothing to change by hand in the tunnel.
- Install the official WireGuard for Windows client and create the tunnel.
- Activate the tunnel before starting stunmesh-go.
- Run stunmesh-go from an Administrator console. The WireGuard service requires the same privilege; without it stunmesh-go fails at the first device access with a "run stunmesh as Administrator" error.
The configuration file is unchanged from the other platforms and the proxy needs no configuration of its
own; set interfaces.<name>.proxy.listen only if you need a fixed outer port for port forwarding or a
port-based firewall.
Important
Deactivating and reactivating a tunnel in the WireGuard UI wipes the endpoints stunmesh-go set, because
the service re-applies the .conf file. Restart stunmesh-go after any tunnel toggle or restart.
Note
ICMP ping monitoring is not implemented on Windows yet; stunmesh-go logs this and continues without it.
Linux, macOS, and FreeBSD can opt into the same UDP proxy Windows always uses by setting
interfaces.<name>.proxy.enabled: true (default false on these platforms). This is required for
full-tunnel setups on macOS and FreeBSD, and optional on Linux for restricted environments that lack
CAP_NET_RAW. See docs.stunmesh.dev for the full-tunnel
guide per platform.
Android is covered by a separate app, stunmesh-android, which embeds this project's STUN core alongside a userspace WireGuard implementation and runs as a VPN service — no root required. It is in early development: install by sideloading the universal APK from its releases page. Only the built-in storage plugins are available there, and exempting the app from battery optimization is recommended for long-lived tunnels.
| Topic | Link |
|---|---|
| Getting started | docs.stunmesh.dev/getting-started |
| Configuration reference (protocols, STUN servers, ping monitoring) | docs.stunmesh.dev/configuration/overview |
| Storage plugins (built-in, exec, shell, dedup) | docs.stunmesh.dev/plugins/overview |
| Writing your own plugin | docs.stunmesh.dev/plugins/exec-protocol |
| Deployment guides (VyOS, macOS, OSPF/VRF) | docs.stunmesh.dev/guides/vyos |
| Building from source & backend selection | docs.stunmesh.dev/reference/build |
| Platform internals (fwmark, BPF capture, listen interfaces) | docs.stunmesh.dev/reference/platform-internals |
make allNote
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.
- auto execute when routing engine notify change
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. See LICENSE (LGPL-3.0) and LICENSE.GPL (GPL-3.0, which the LGPL supplements).
Contributions are covered by the CLA.
WireGuard is a registered trademark of Jason A. Donenfeld.