No description
  • Go 72%
  • Shell 18.8%
  • JavaScript 8.6%
  • Makefile 0.6%
Find a file
Ladislav Brodecký f6a8967228 docs(readme): drop the secrets/placeholders warning block
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>
2026-06-04 20:14:54 +02:00
.claude chore: vyvojovy zaklad — CLAUDE.md, 5 workflowu, overena T1 lab, scaffolding 2026-05-31 16:23:55 +02:00
.forgejo/workflows feat(feed): native OpenWrt apk-v3 feed (signed) on the generic registry — the LuCI "Software" update list 2026-06-04 19:50:44 +02:00
cmd/gobond feat: MPTCP subflow re-bond on WAN-up — flapped WAN rejoins the bond (the #1 correctness gap) 2026-06-04 00:07:20 +02:00
deploy feat(feed): native OpenWrt apk-v3 feed (signed) on the generic registry — the LuCI "Software" update list 2026-06-04 19:50:44 +02:00
docs docs: English self-contained README as the only user doc; drop redundant operator/dev .md 2026-06-04 13:07:48 +02:00
internal chore: scrub secrets + remove scratch files before push 2026-06-04 01:22:37 +02:00
lab docs: English self-contained README as the only user doc; drop redundant operator/dev .md 2026-06-04 13:07:48 +02:00
openwrt/package feat(luci): expose the gob0 AQM toggle in the GUI (turn off built-in CAKE for qosmate) 2026-06-04 20:12:14 +02:00
test-evidence test(F2): real-HW aggregation over O2+Artemis — mechanism+latency work, throughput needs F3 2026-06-01 00:41:36 +02:00
.gitattributes feat(F1): M1.0 — single-path plaintext L3 tunnel, lab-verified inbound convergence 2026-05-31 18:40:12 +02:00
.gitignore chore: scrub secrets + remove scratch files before push 2026-06-04 01:22:37 +02:00
CLAUDE.md docs: English self-contained README as the only user doc; drop redundant operator/dev .md 2026-06-04 13:07:48 +02:00
go.mod feat(fec): Reed-Solomon FEC core (encoder + decoder) — unit-tested 2026-06-02 11:22:11 +02:00
go.sum feat(fec): Reed-Solomon FEC core (encoder + decoder) — unit-tested 2026-06-02 11:22:11 +02:00
OMR-shrnuti-a-navrh-Go-nadstavby.md docs: sanitize internal LAN addresses to placeholders 2026-05-31 13:17:28 +02:00
README.md docs(readme): drop the secrets/placeholders warning block 2026-06-04 20:14:54 +02:00

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-go crypto+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 6500065535 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 .apk carries the tag version) → upgrade gobond; 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 gob0 to an external QoS like qosmate / SQM: select off, Save & Apply, then point qosmate at the gob0 interface and set its bandwidth to the bonded aggregate (a little under the real total). Don't run both — off leaves gob0 on 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 with write: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.