- Go 72%
- Shell 18.8%
- JavaScript 8.6%
- Makefile 0.6%
Contributor/operating note, not user-facing content. The secret-handling rule still lives in CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| cmd/gobond | ||
| deploy | ||
| docs | ||
| internal | ||
| lab | ||
| openwrt/package | ||
| test-evidence | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| go.mod | ||
| go.sum | ||
| OMR-shrnuti-a-navrh-Go-nadstavby.md | ||
| README.md | ||
gobond — lean Go multi-WAN bonding (an OpenMPTCProuter alternative)
One encrypted L3 tunnel across multiple WAN links, userspace UDP bonding with a custom scheduler, and a
VPS edge that NATs traffic in and out of the tunnel. A single Go binary, two roles (router / vps), packaged
for stock OpenWrt (no fork). A lean, robust alternative to OpenMPTCProuter (OMR).
What it does
- Aggregation of multiple WANs into higher throughput + failover on link loss (download and upload, TCP and UDP).
- One public egress IP on the VPS (hides the real WAN IPs).
- Inbound hosting — a server behind the router (TS3, game servers) reachable from the internet via the VPS (DNAT into the tunnel).
- Dual-stack IPv4 + IPv6 end-to-end.
- Encryption: Noise IK + ChaCha20-Poly1305 (forward secrecy, rekey, anti-replay) —
wireguard-gocrypto+TUN as a library, with a custom datagram bonding scheduler (pin-only multi-flow + optional FEC on lossy links). - TCP auto-bonds via kernel-MPTCP dual-PEP (a single flow aggregates all WANs + seamless failover); UDP / ICMP / P2P / inbound stay on the L3 tunnel — with no configuration. Path MTU auto-tunes (PLPMTUD).
- Optional: adaptive FEC, CAKE on the decrypted
gob0, capacity auto-rate.
Status
Working, verified on real hardware over the internet (OpenWrt router + Debian VPS, real WANs): encrypted tunnel, multipath bonding, ~ms WAN-down failover, inbound hosting, dual-stack. Single binary, self-programming VPS edge, OpenWrt package (netifd proto + procd + UCI + LuCI). Performance tuning on a small VPS is ongoing — measure real throughput, don't treat metrics as final.
Install
You need a VPS (Debian/Ubuntu, public IPv4, ideally a routed IPv6 prefix) and an OpenWrt router. Both sides are a single command.
1) VPS — one command, as root
Downloads the prebuilt binary (or builds from source as a fallback), installs a hardened systemd service
(CAP_NET_ADMIN/NET_RAW only, NoNewPrivileges, ProtectSystem=strict), generates the keypair, and prints the
values to paste into the router:
curl -fsSL https://git.xn--la-mia8p.eu/ladab/gobond/raw/branch/main/deploy/vps-edge/bootstrap.sh | sh
Re-running is safe (it keeps the keypair; ROTATE_KEYS=1 regenerates it, which means you must re-pair the router).
VPS-internal ports live in the reserved 65000–65535 range (underlay :65535, admin SSH :65022), so every port
below 65000 is free to forward to a LAN host. The installer also frees port 22 by moving SSH to :65022 (opt out
with KEEP_SSH_22=1) and sets a minimal host firewall.
⚠️ If you enable the optional TCP PEP (
tcp_pep), block its port (50080) from the WAN — it is off by default.
2) Router (OpenWrt) — install, then configure in LuCI
Fetch the installer and run it with no arguments — it installs both packages (gobond + luci-app-gobond,
from the feed or the release) and stops:
wget https://git.xn--la-mia8p.eu/ladab/gobond/raw/branch/main/deploy/router/openwrt-install.sh
sh openwrt-install.sh
Then open LuCI → Network → gobond → Settings and fill in: the VPS server, the router private key +
VPS public key the VPS installer printed, and your WANs (each with optional Down/Up, or a Measure
button). Save & Apply — the tunnel comes up and reconfigures itself. The firewall zone, lan → tunnel
forwarding and default route are created automatically by the package (uci-defaults + an ifup hotplug).
Prefer the command line? Run it with the values inline and it configures the interface for you too:
SERVER=<vps_ip> PEER_PUBLIC_KEY=<VPS public key> PRIVATE_KEY=<Router private key> \ WANS="wan:150 wwan0:30" sh openwrt-install.sh
WANS= space-separated<openwrt_iface>:<down_mbit>[:<up_mbit>](seeds the scheduler weights).
That's it — TCP auto-bonds via MPTCP, UDP/ICMP/P2P stay on L3, the MTU auto-tunes, and WAN-down failover is ~ms.
To force plain L3 (no MPTCP PEP): set Scheduler-adjacent option mptcp_pep_disable (or option mptcp_pep_disable '1').
Updating
Ship a new version by tagging — CI builds and publishes the release:
git tag v0.3.0 && git push origin v0.3.0 # builds the binaries + .apk (version 0.3.0) and publishes a release
Then one command per machine (keys and config are preserved):
- VPS — re-run the same one-liner (downloads the new binary, atomically swaps it, restarts the service):
curl -fsSL https://git.xn--la-mia8p.eu/ladab/gobond/raw/branch/main/deploy/vps-edge/bootstrap.sh | sh - Router — the gobond binary lives inside the
.apk, so a package upgrade is the binary upgrade, and the package re-ups the interface by itself. Either LuCI → System → Software → Updates (the new version shows because the.apkcarries the tag version) → upgradegobond; or, over SSH:sh openwrt-install.sh # no variables = UPDATE mode: apk upgrade gobond + reactivate, keys untouched
Usage
# router: tunnel status (established, paths, weights, RTT, loss)
gobond -config /var/run/gobond-wanbond.json -status
# measure real speed (through OpenWrt), per-WAN or the bonded aggregate
gobond-speedtest wan # one link only (source-bound)
gobond-speedtest # bonded tunnel (aggregate)
gobond-speedtest --set wan # measure + write the real rate into the config (seeds the scheduler weights)
# VPS: edge status
gobond -config /etc/gobond/vps.json -status
systemctl status gobond-vps
Port forwards are managed from OpenWrt (firewall redirects) and propagate to the VPS automatically. Per-WAN speeds are either seeded manually or detected by auto-rate.
QoS / SQM (bufferbloat)
Shape the decrypted tunnel gob0, never the per-WAN interfaces — gob0 is where all traffic converges as
one cleartext aggregate (the encrypted per-WAN underlay is opaque, and the scheduler splits a flow across WANs,
so per-WAN shaping fights it). gobond defaults to CAKE on gob0 out of the box.
In LuCI → Network → gobond → Settings → Queue management (gob0):
- CAKE (built-in) — default; good bufferbloat control with zero setup.
- off — hand
gob0to an external QoS like qosmate / SQM: select off, Save & Apply, then point qosmate at thegob0interface and set its bandwidth to the bonded aggregate (a little under the real total). Don't run both — off leavesgob0on the default qdisc so qosmate owns it without two qdiscs colliding.
Upload (LAN→tunnel) shaping on gob0 works directly; download bufferbloat is best handled VPS-side (gobond's
download pacing), with qosmate ingress on gob0 as a complement. The aggregate rate is variable (which WANs are
up), so set a fixed external rate conservatively.
Releasing & the OpenWrt package feed (maintainer)
A git push origin <tag> (tags matching v*) triggers .forgejo/workflows/release.yml, which cross-builds the
gobond-linux-{amd64,arm64} binaries and the OpenWrt .apk (version stamped from the tag) and publishes a Forgejo
Release with all of them as assets. The VPS installer downloads the binary; the router installer pulls the .apk
from the release. A version tag is required to release — a plain git push origin main does not build.
To also list gobond in LuCI → Software (apk add gobond by name, with in-LuCI updates), CI publishes a
native OpenWrt apk-v3 feed (a signed packages.adb index + the .apks + the public key) to the repo's Forgejo
generic registry. (Forgejo's "Alpine registry" only speaks Alpine apk-v2 and rejects OpenWrt apk-v3, so we
sign + index the feed ourselves with apk adbsign/mkndx.) Two one-time repo Actions secrets enable it:
REGISTRY_TOKEN— a Personal Access Token withwrite:package(Forgejo → Settings → Applications → Generate Token), to upload.APK_SIGN_KEY— an RSA private key that signs the feed. Generate once and paste the whole PEM in:openssl genrsa 2048 # copy the output into the APK_SIGN_KEY secret
Without these you are not blocked — the .apk still ships as a release asset and openwrt-install.sh pulls it
from the release; you just don't get the in-LuCI update list. With them, the router enables the feed automatically
on first install (openwrt-install.sh adds the repo + fetches the feed's public key); the one-time manual setup is:
arch=$(apk --print-arch) # e.g. x86_64
feed="https://git.xn--la-mia8p.eu/api/packages/ladab/generic/gobond-feed/$arch"
wget -O /etc/apk/keys/gobond-feed.pem "$feed/public-key.pem"
echo "$feed" >> /etc/apk/repositories
apk update && apk add gobond luci-app-gobond
Design docs
Architecture, locked decisions and the roadmap live in docs/ (in Czech): overview, MPTCP-vs-userspace
aggregation, inbound UDP/NAT, the OpenWrt package, architecture & roadmap, and auto-rate.