A fast, seamless reverse proxy for Minecraft: Bedrock Edition, written in Rust.
Rift sits in front of your PocketMine-MP servers and moves players between them without a reconnect β no "Disconnected" screen, no re-login, no resource-pack reload. It's a lightweight, high-performance alternative to WaterdogPE.
Status: actively developed. Core proxying and seamless transfer are verified in-game. See the Roadmap for what's done vs. planned.
Rift optimizes for a few measurable targets (observe them at /metrics):
- Zero hot-path allocation β forwarding a packet allocates nothing on the heap (verify with a
--features profilingbuild βalloc_count). - No work for uninteresting packets β packets Rift doesn't touch are never decoded into objects; they pass through as raw bytes.
- Minimal forward latency and 0% drop for reliable traffic.
- Lower CPU than WaterdogPE at equivalent load.
On "zero-copy": Rift is application-level zero-copy β a copy-minimized data path. Within the process, forwarded bytes are moved (ref-counted), not copied. The unavoidable NIC β kernel β userspace copy still happens; true kernel-bypass zero-copy (io_uring registered buffers) is on the roadmap.
- β‘ High-performance Bedrock / RakNet proxy written in Rust.
- π Packet pass-through fast path β zero object creation for non-intercepted packets.
- π― Selective packet interception β only the few packets Rift must act on are decoded.
- π Seamless server transfer β switch backends with no reconnect; game mode, game rules, boss bars and scoreboards carry over.
- π§© PMMP-optimized β deterministic entity IDs via the drop-in RiftSupport plugin.
- π Cross-platform β builds on Linux & Windows; fully static
muslbinaries run on any distro. - π Built-in observability β
/metrics+ a live web dashboard, per-player ping, JSONL metrics history, and CPU / allocation profiling hooks. - ποΈ Live console β
info/list/transfer/kick/stop. - π‘οΈ Production-minded β graceful shutdown, panic-free parsing, DoS-resistant fragment reassembly, reliable-packet de-dup.
- Build (or grab a release binary):
cargo build --releaseβtarget/release/rift - Configure:
cp config.example.toml config.toml, then point[servers]at your backend addresses. - Run:
./rift config.toml - Prepare each backend PMMP server: set
enable-encryption: falseand install the RiftSupport plugin (one drop-in β see Requirements). - Connect your Bedrock client to the proxy's address, and switch servers seamlessly.
For production (auto-restart, console, firewall), see Run and dist-linux/DEPLOY.md.
Rift terminates RakNet itself: clients connect to Rift, and Rift opens its own RakNet connection to each downstream server. Game packets are shuttled as opaque bytes β only login, transfer, resource-pack and a few spawn-related packets are decoded.
Seamless transfer (triggered by a downstream TransferPacket, or the console transfer command):
- Rift connects to the target server and drives the handshake to full spawn, buffering the spawn stream.
- The previous server's client-side state is torn down: actor entities (
RemoveActor), boss bars, scoreboards, and weather β so nothing lingers as a ghost. - The client is walked through a dimension flip (to a dummy dimension and back). Each change is sent the way the client needs to actually complete it β
ChangeDimensionβNetworkChunkPublisherUpdateβ chunks βPlayStatusβ dimension-change ACK β which fully re-initializes it on the new world (this mirrors WaterdogPE's sequence). - The downstream connection is swapped,
SetLocalPlayerAsInitializedtriggers the real spawn, and the buffered spawn stream is replayed.
Entity IDs are not rewritten. Instead, every server assigns the same player the same runtime ID β crc32(XUID) & 0x7FFFFFFFFFFFFFFF β so the client's view stays consistent across servers (see Requirements).
- Keep the fast path small β the overwhelming majority of packets (movement, chunks, entity updates) are never decoded. Batches larger than a threshold (
features.max_decode_batch_bytes, default 8 KB;0decodes everything) are forwarded opaquely (no decompress, no decode, no allocation β the receivedBytesare passed straight through); only the small batches that can carry a packet Rift acts on (transfer, entity Add/Remove, resource-pack) are decompressed and peeked via a single interest-bitmap lookup. - Layer boundary: RakNet is transport-only β
vendor/rift-raknetknows nothing about Minecraft (no0xfe/game-packet assumptions); it moves opaque payloads with reliability/ordering/ACK. The Minecraft packet layer and all interception live in the proxy (src/intercept.rs,src/packets.rs).UDP β RakNet β Minecraft packet layer β intercept. - Zero-copy forward, verifiable β
recv()yieldsBytes; the fast path forwards them withsend_bytes()(ref-counted slice, no copy, no clone). Only the slow path (a packet Rift rewrites) allocates. A--features profilingbuild exposesalloc_count/alloc_bytesat/metricsso the zero-allocation hot path is checkable, not just claimed. - Own the transport β the vendored RakNet fork is tuned directly: copy-minimized framing, bounded fragment reassembly (DoS-resistant), reliable-packet de-duplication, configurable ACK tick.
- PMMP-first β the transfer model and deterministic entity-ID scheme are built around PocketMine-MP.
- Measure, then optimize β metrics first: throughput, forward-latency histogram (p50/p95/p99), allocation counters, JSONL time-series, and a load tester. Heavier work (pooling, worker-sharding, io_uring) only after real-server measurement. Predictable latency over feature bloat.
Each downstream PMMP server behind Rift needs two things:
- Encryption off β
enable-encryption: falseinpocketmine.yml. Rift forwards the client's login token verbatim, so the XUID is preserved. - Deterministic entity IDs β every server must assign the same player the same runtime entity id, so the client's view of "itself" stays consistent across a transfer.
Drop the downstream/RiftSupport plugin into each backend server. It:
- swaps in a
Playerthat sets a deterministic id (crc32(XUID)), and - warns you on startup if encryption is still on, or if a custom
Playerclass needs the manual step below.
Load it as a folder with DevTools, or build a .phar.
If your server already uses its own Player subclass, RiftSupport won't replace it β add this to that class instead:
protected function initEntity(CompoundTag $nbt) : void {
parent::initEntity($nbt);
$xuid = $this->getXuid();
$key = $xuid !== "" ? $xuid : $this->getName();
$this->id = crc32($key) & 0x7FFFFFFFFFFFFFFF;
}Either way this must apply on every downstream server, or a transferred player's own entity (and warps) will break.
Rift uses a split trust model: encryption is intentionally dropped on the trusted hop to avoid double-encryption overhead, while the untrusted hop stays protected.
- Proxy β backend: plaintext β backends run
enable-encryption: false. The proxy and backends are expected to sit on the same trusted private network, so encrypting this hop would only add cost. - Client β proxy: the public hop. The intended model is to terminate Bedrock encryption at the proxy here, so the client link is encrypted while the backend link stays plaintext.
β οΈ Current status: client-side encryption termination is not wired yet β Rift currently runs plaintext on both hops. Until it lands, deploy Rift where the clientβproxy path is trusted (LAN / VPN / a fronting layer). The crypto layer (src/crypto.rs, P-384 ECDH + AES-CTR) is in the tree; wiring it is the next security milestone (see Roadmap).
Requires a recent Rust toolchain.
cargo build --release
# β target/release/riftFaster production build (glibc) β enables the zlib-ng inflate backend (~1.5β2Γ faster down-stream decode):
cargo build --release --features fast-inflateNeeds a C toolchain + cmake. zlib-ng links statically (no runtime dependency). Like mimalloc it's a C dep, so it's off by default and not used by the musl build below.
Fully static Linux binary (runs on any distro, no glibc-version or musl-gcc worries):
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl --no-default-features
# β target/x86_64-unknown-linux-musl/release/rift--no-default-features drops the optional mimalloc allocator (Rift's only C dependency), yielding a pure-Rust static build.
cp config.example.toml config.toml
# edit config.toml β point [servers] at your downstream addressesAll options are documented in config.example.toml.
Development:
./rift config.tomlProduction β two options, both in dist-linux/ (see dist-linux/DEPLOY.md):
- screen +
start.shβ recommended if you want the live console. Auto-restarts on crash.screen -S rift ./start.sh
- systemd +
setup.shβ hands-off, starts on boot, no console (manage via dashboard +systemctl).sudo bash setup.sh # installs to /opt/rift, opens firewall, registers + starts the service
| Command | Description |
|---|---|
info |
uptime, player count, throughput, per-server breakdown |
list |
connected players (#id name ip β server) |
transfer <name|id> <server> |
move a player to another channel |
kick <name|id> |
disconnect a player |
stop |
graceful shutdown |
Set web_addr in config.toml (e.g. "0.0.0.0:8080"):
http://<host>:8080/β auto-refreshing dashboardGET /metricsβ JSON: active/peak players, throughput, packet rate, avg packet size, avg forward latency, transfers, per-server (plusalloc_*on a profiling build)GET /playersβ JSON: id, name, ip, server, ping (RTT), uptime
/playersexposes player names and IPs β keep the port behind a firewall if it's reachable publicly.
Rift follows a measure-then-optimize policy, so the tooling to gather real data is built in:
- Time-series history β set
history_fileinconfig.tomlto append a metrics snapshot (one JSON object per line) everyhistory_interval_secs. Leave it on in production to collect throughput / latency / player-count history over time. - CPU profiling β to find which function is hot, build the symbol-included
profilingprofile and sample with Linuxperf. SeePROFILING.md. - Allocation profiling β a
--features profilingbuild exposesalloc_count/alloc_bytesat/metricsto verify the hot path stays allocation-free. - Load testing β
tools/riftbenchconnects N offline bots through Rift and generates realistic player traffic, so you can find the ceiling yourself (requires an offline-mode backend).
src/ proxy core β intercept, transfer, packets, web, console, registry, ...
vendor/rift-raknet/ vendored, patched fork of rust-raknet (Bedrock RakNet)
downstream/RiftSupport/ drop-in PMMP plugin β deterministic entity ids for backends
tools/riftbench/ load tester β N offline bots over gophertunnel
dist-linux/ deploy assets β start.sh, setup.sh, rift.service, DEPLOY.md
PROFILING.md CPU / allocation profiling workflow
Rift targets the Bedrock protocol used by current PocketMine-MP builds. The specific packet IDs Rift decodes live in src/ and may need updating when Bedrock bumps its protocol.
Core
- β RakNet transport Β· login Β· resource-pack phase
- β Seamless server transfer β WaterdogPE-style dimension-flip + chunk-publisher/ACK completion, state teardown (entities, boss bars, scoreboards, weather), verified in-game
- β Metrics endpoint Β· web dashboard Β· live console
- β¬ Client-side encryption termination (see Security)
- β¬ Config hot-reload
Performance
- β mimalloc (optional) Β· packet pass-through fast path
- β Application-level zero-copy (Bytes data path)
- β Bounded fragment reassembly Β· reliable-packet de-dup
- β
Profiling build with allocation counters (
--features profiling) - β¬ Buffer / packet pool Β· arena allocation
- β¬
sendmmsg/recvmmsgΒ· worker sharding +SO_REUSEPORTΒ· CPU affinity / NUMA - β¬ io_uring Β· libdeflate
Networking
- β Multiple backends + transfer Β· configurable MTU
- β¬ Health checks Β· auto-reconnect Β· weighted routing / load balancing
- β¬ Proxy Protocol Β· MTU auto-negotiation
Operations
- β Graceful shutdown
- β
Metrics history (JSONL time-series) Β· CPU/alloc profiling guide Β· load tester (
tools/riftbench) - β¬ Connection rate limiting
- β¬ Prometheus / OpenTelemetry Β· JSON logging Β· admin API Β· graceful (zero-downtime) restart
β οΈ Resource-pack serving is implemented but not yet verified in-game (off by default). Linux performance items are deferred until real-server measurement.
MIT.
vendor/rift-raknet/ is a patched fork of rust-raknet by b23r0, also under the MIT license (see vendor/rift-raknet/LICENSE).