OpenFlux Go engine (client/exit-node/controlplane) - fork of p1neappleXpress/OpenFlux
  • Go 78.8%
  • Svelte 9.6%
  • HTML 5%
  • TypeScript 4.6%
  • CSS 0.9%
  • Other 1.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
sidylink2 72d50ec65b Don't evict activePorts the instant our own FIN/RST goes out
Yesterday's fix made FIN/RST correctly delete the port it actually stores
under (was deleting the wrong key entirely, a no-op). But TCP allows
half-close: after we relay the client's own FIN out, it can still be
legitimately waiting on the peer's trailing data or its own FIN. Deleting
the port entry the instant our FIN/RST leaves means readLoop starts
rejecting that still-expected reply as "port not active" - a plausible
match for "connects, but traffic only goes one way, then eventually the
server lets it go" once a long-lived session (sitting in Telegram) finally
closes one of its connections.

Eviction is now delayed by a grace period and generation-checked
(CompareAndDelete against the generation captured at FIN/RST time), so a
port reused by a new connection before the grace period elapses is left
alone instead of having its fresh binding yanked out from under it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 09:27:09 +03:00
controlplane Fix a crash-on-malformed-packet in the raw socket reader, and a rate-limiter leak 2026-09-19 18:03:42 +03:00
deployssh Scale to 1000+ keys, fix a node-wide traffic outage, trim comments hard 2026-09-19 10:10:11 +03:00
gateway Wire the client's configured MTU into the tunnel - it was silently dropped 2026-09-20 08:31:28 +03:00
mobile Wire the client's configured MTU into the tunnel - it was silently dropped 2026-09-20 08:31:28 +03:00
network Scale to 1000+ keys, fix a node-wide traffic outage, trim comments hard 2026-09-19 10:10:11 +03:00
nodeagent Make raw mode's per-key port range configurable (default 96) 2026-09-19 15:20:13 +03:00
socks5 Fix SOCKS5 server dropping requests split across TCP packets 2026-09-19 12:27:18 +03:00
transport Stop matching other participants' real cursors as tunnel data 2026-09-19 19:44:36 +03:00
tunnel Don't evict activePorts the instant our own FIN/RST goes out 2026-09-20 09:27:09 +03:00
utils Scale to 1000+ keys, fix a node-wide traffic outage, trim comments hard 2026-09-19 10:10:11 +03:00
.gitignore Initial import from p1neappleXpress/OpenFlux (fork) 2026-09-10 21:18:37 +03:00
build_android.sh Initial import from p1neappleXpress/OpenFlux (fork) 2026-09-10 21:18:37 +03:00
build_android_aar.sh Scale to 1000+ keys, fix a node-wide traffic outage, trim comments hard 2026-09-19 10:10:11 +03:00
build_ios.sh Trim comment clutter across the codebase 2026-09-17 15:08:18 +03:00
COPYRIGHT Initial import from p1neappleXpress/OpenFlux (fork) 2026-09-10 21:18:37 +03:00
go.mod Fix production bugs, scale exit nodes, and speed up transports 2026-09-16 19:06:52 +03:00
go.sum Fix production bugs, scale exit nodes, and speed up transports 2026-09-16 19:06:52 +03:00
LICENSE Initial import from p1neappleXpress/OpenFlux (fork) 2026-09-10 21:18:37 +03:00
main.go Make raw mode's per-key port range configurable (default 96) 2026-09-19 15:20:13 +03:00
NOTICE Initial import from p1neappleXpress/OpenFlux (fork) 2026-09-10 21:18:37 +03:00
README.md Mark the raw socket's own packets so the RST-drop iptables rule stops eating them 2026-09-19 19:14:54 +03:00
README.ru.md Mark the raw socket's own packets so the RST-drop iptables rule stops eating them 2026-09-19 19:14:54 +03:00

OpenFlux Server

English | Русский

A fork of p1neappleXpress/OpenFlux. Network stack research tool: TCP tunnel with pluggable transports, plus a multi-user control plane and gomobile bindings for the Android app.

Sibling repos: openflux-app (the Android client) and openflux-deploy (rolls this repo's controlplane out onto a VPS).

Overview

Client (SOCKS5) --> Transport --> Exit Node --> Internet

TCP packets are sent via Transport. Available transports (--transport):

  1. yandex/yandex_multistream/volga - send packets via Yandex Docs cursor messages;
  2. oneme (Max) - sends packets via WebRTC DataChannel (desktop client/exit-node only - the Android app doesn't support it; see mobile/mobile.go's package comment for why);
  3. cupsonline - sends packets via cups.online's collaborative interview-room cursor sync (desktop client/exit-node only, ported from upstream);
  4. mailru - sends packets via Mail.ru Docs cursor messages, the same coauthoring-protocol family as yandex (desktop client/exit-node only, ported from upstream).

Client side runs a SOCKS5 proxy, exit node decapsulates and forwards packets to destination point.

Requirements

  1. Golang v. 1.26.3+ - for building the desktop client / exit-node binary (universal-bypass-tool);
  2. Android NDK v.27.0.12077973+ - for building the .aar the Android app embeds (./build_android_aar.sh);
  3. XCode v. 26.6+ - for building the iOS client binary;
  4. A Linux VPS/VDS for the exit node and, if you want the multi-user control plane, for controlplane too (see openflux-deploy).

Structure

main.go
transport/
├── transport.go      # Transport interface
├── yandex/           # Yandex Docs backend (also Volga, yandex_multistream)
├── oneme/            # MAX Messenger backend (desktop only)
├── cupsonline/       # cups.online backend (desktop only, ported from upstream)
└── mailru/           # Mail.ru Docs backend (desktop only, ported from upstream)
tunnel/
├── tunnel.go         # TCP tunnel core
├── endpoint.go       # Virtual NIC
└── rawsocket.go      # Raw socket (exit node)
socks5/                # SOCKS5 server (desktop client)
gateway/               # TUN-based transparent proxy (Android client, via VpnService)
mobile/                # gomobile bind entry point consumed by openflux-app
nodeagent/             # Exit-node orchestrator for managed (controlplane) mode
controlplane/          # Multi-user key/traffic/token service + admin panel - separate Go
│                      # module, see controlplane/README.md
network/               # Checksums, packet parsing
utils/                 # Debug logging

Build (desktop client / exit-node binary)

go mod tidy
go build -o universal-bypass-tool .

Build for Android

See openflux-app's README - ./build_android_aar.sh here builds mobile/ into an .aar via gomobile bind for that repo to embed.

Build for iOS (client binary)

export XCODE_PATH="<your Xcode.app path>" # optional, defaults to /Applications/Xcode.app
./build_ios.sh

Usage

Setting up an exit node

The exit node reaches the real internet in one of two modes (--mode):

  • raw (default) — gvisor forwards raw IP packets through a real raw socket (needs root) and its own NAT/forwarding. Carries any IP protocol the client sends - this is how general UDP relay (not just DNS) currently works - but the kernel has no socket for these gvisor-terminated connections and sends a real RST on every reply unless suppressed; see below. Only tested on Linux.
  • proxy — each TCP flow is terminated locally in gvisor and re-originated with a plain net.Dial to the real destination. No root, no raw socket, no RST-drop rule needed at all. TCP only: a UDP packet gets gvisor's own default port-unreachable response instead of being relayed (which incidentally makes QUIC-preferring apps fall back to TCP fast instead of stalling). Works on Linux, Windows, macOS.

Raw mode's kernel-generated RSTs must be suppressed, but scoped, not host-wide - a blanket -j DROP on all outbound RSTs makes every closed port on the box answer with silence (a port scanner sees "filtered" instead of "closed") and stops the host resetting any of its own other connections. Neither -m owner --uid-owner nor -s <ip> can scope this correctly: the kernel-generated RSTs have no owning socket, and the raw socket sends its own legitimate RSTs from that same IP too - either match drops both, silently EPERM'ing our own connection resets and leaving real peers thinking a torn-down connection is still open. The raw socket marks its own packets (SO_MARK, see tunnel/rawsocket_linux.go) specifically so the rule can tell them apart:

# raw mode (default):
sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -m mark ! --mark 0x2547 -j DROP
sudo ./universal-bypass-tool --exit-node --url "YOUR_YANDEX_DOC_URL" --debug

# proxy mode - no root, no iptables rule, but no general UDP relay either:
./universal-bypass-tool --exit-node --mode proxy --url "YOUR_YANDEX_DOC_URL" --debug
  1. Only the legacy Yandex document editor is supported (toggle this from the interface).

Setting up a desktop client

./universal-bypass-tool --client --url "YOUR_YANDEX_DOC_URL" --socks5 :1080 --debug

Then set up a SOCKS5 proxy in your browser at localhost:1080.

Flags

Flag Default Description
--client Run as client
--exit-node Run as exit node
--socks5 :1080 SOCKS5 listen address
--url https://localhost Document URL (Yandex Docs)
--maxToken `` Auth token (Max)
--maxUid `` User ID (Max)
--debug false Enable verbose logging
--transport yandex Select transport backend (yandex, volga, oneme, yandex_multistream, cupsonline, mailru)
--managed false Exit node only: fetch active keys from a controlplane instance instead of a single --url
--control-url `` Managed mode: base URL of the openflux-control service
--node-token `` Managed mode: this node's bearer token from controlplane
--mode raw Exit node only: raw (needs root, general UDP relay) or proxy (no root, TCP only)
--local-ip `` Raw mode only: exit node egress IP, for a box with more than one
--port-range-size 96 Managed raw mode only: outbound ports reserved per concurrent key - lower fits more keys on this node (~65000/size), higher tolerates one key opening more simultaneous connections at once (e.g. Telegram loading media) before new ones start failing
--codec legacy Wire codec for --transport volga/oneme/cupsonline/mailru: legacy (per-packet LZ4, unchanged) or batched (coalesce bursts into one zstd-compressed message per transport send - see below). Both ends must agree. Ignored for yandex/yandex_multistream - see below.

Ported from upstream p1neappleXpress/OpenFlux: batched coalesces a burst of outgoing tunnel packets (plus a short linger window to catch stragglers - both tunable via OPENFLUX_BATCH_BYTES / OPENFLUX_BATCH_COUNT / OPENFLUX_BATCH_LINGER_MS env vars) into a single zstd-compressed message per transport send, instead of one message per packet. Applies to volga/oneme/cupsonline/mailru only; a client and exit node must run the same --codec for these - they can't decode each other's frames otherwise.

The yandex/yandex_multistream transports don't use --codec at all - they already coalesce internally, and auto-negotiate whole-batch zstd compression with the peer instead of compressing each packet before batching: once a peer's keepalive proves it understands the newer format, several raw packets are framed together and zstd-compressed as one unit rather than LZ4-compressed one at a time before being batched - strictly better compression (it can exploit redundancy between packets in the batch, not just within one) at no compatibility cost. An old client or exit node that's never seen this feature is unaffected: the bytes it sends and receives are untouched, and a new peer talking to it just keeps using the older per-packet format it always used, indefinitely if that peer never upgrades. Nothing to configure - this is automatic and safe to roll out to only one side of a deployment at a time.

This applies with e2e_encryption on too, not just plain keys: instead of encrypting each compressed packet independently and then batching the ciphertexts (what the traditional wrapping does, and what this falls back to until negotiated), a whole batch of raw packets is compressed and THEN encrypted as one sealed unit once the peer proves (via a second, separate capability check) it does encrypted self-compression for this key - plaintext compression markers never leave the process either way, and a peer still on the traditional wrapping decrypts and decompresses the fallback format exactly as before.

Multi-user deployments (controlplane)

For running many keys/users behind a fleet of exit nodes — auth tokens, per-key traffic accounting, enabling/disabling keys, a web admin panel, and an ingestion API for third-party key generators — see controlplane/README.md. Exit nodes opt into this with --exit-node --managed --control-url ... --node-token ...; the plain single---url flow above still works unchanged for manual/one-off use. To actually roll controlplane out onto a VPS (Postgres, systemd, Nginx, Let's Encrypt), see openflux-deploy.

Implementing custom transports

You are free to implement the Transport interface from transport/transport.go and register your custom transport in main.go's switch block.

License

This project is licensed under the GNU General Public License v3.0 or later. See LICENSE for the full text.

Third-party licenses are listed in NOTICE.

Disclaimer

Educational use only. Test on your own machines and networks.