Skip to content

ahnsunggwan45/Rift

Repository files navigation

Rift

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.

Goals

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 profiling build β†’ 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.

Features

  • ⚑ 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 musl binaries 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.

Quick start

  1. Build (or grab a release binary): cargo build --release β†’ target/release/rift
  2. Configure: cp config.example.toml config.toml, then point [servers] at your backend addresses.
  3. Run: ./rift config.toml
  4. Prepare each backend PMMP server: set enable-encryption: false and install the RiftSupport plugin (one drop-in β€” see Requirements).
  5. 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.

How it works

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):

  1. Rift connects to the target server and drives the handshake to full spawn, buffering the spawn stream.
  2. The previous server's client-side state is torn down: actor entities (RemoveActor), boss bars, scoreboards, and weather β€” so nothing lingers as a ghost.
  3. 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).
  4. The downstream connection is swapped, SetLocalPlayerAsInitialized triggers 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).

Design principles

  • 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; 0 decodes everything) are forwarded opaquely (no decompress, no decode, no allocation β€” the received Bytes are 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-raknet knows nothing about Minecraft (no 0xfe/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() yields Bytes; the fast path forwards them with send_bytes() (ref-counted slice, no copy, no clone). Only the slow path (a packet Rift rewrites) allocates. A --features profiling build exposes alloc_count/alloc_bytes at /metrics so 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.

Requirements

Each downstream PMMP server behind Rift needs two things:

  1. Encryption off β€” enable-encryption: false in pocketmine.yml. Rift forwards the client's login token verbatim, so the XUID is preserved.
  2. 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.

Easiest: the RiftSupport plugin

Drop the downstream/RiftSupport plugin into each backend server. It:

  • swaps in a Player that sets a deterministic id (crc32(XUID)), and
  • warns you on startup if encryption is still on, or if a custom Player class needs the manual step below.

Load it as a folder with DevTools, or build a .phar.

Manual (servers with a custom Player class)

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.

Security / trust model

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).

Build

Requires a recent Rust toolchain.

cargo build --release
# β†’ target/release/rift

Faster production build (glibc) β€” enables the zlib-ng inflate backend (~1.5–2Γ— faster down-stream decode):

cargo build --release --features fast-inflate

Needs 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.

Configure

cp config.example.toml config.toml
# edit config.toml β€” point [servers] at your downstream addresses

All options are documented in config.example.toml.

Run

Development:

./rift config.toml

Production β€” 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

Console commands

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

Web monitoring

Set web_addr in config.toml (e.g. "0.0.0.0:8080"):

  • http://<host>:8080/ β€” auto-refreshing dashboard
  • GET /metrics β€” JSON: active/peak players, throughput, packet rate, avg packet size, avg forward latency, transfers, per-server (plus alloc_* on a profiling build)
  • GET /players β€” JSON: id, name, ip, server, ping (RTT), uptime

/players exposes player names and IPs β€” keep the port behind a firewall if it's reachable publicly.

Performance measurement

Rift follows a measure-then-optimize policy, so the tooling to gather real data is built in:

  • Time-series history β€” set history_file in config.toml to append a metrics snapshot (one JSON object per line) every history_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 profiling profile and sample with Linux perf. See PROFILING.md.
  • Allocation profiling β€” a --features profiling build exposes alloc_count / alloc_bytes at /metrics to verify the hot path stays allocation-free.
  • Load testing β€” tools/riftbench connects N offline bots through Rift and generates realistic player traffic, so you can find the ceiling yourself (requires an offline-mode backend).

Project layout

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

Compatibility

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.

Roadmap

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.

License

MIT.

vendor/rift-raknet/ is a patched fork of rust-raknet by b23r0, also under the MIT license (see vendor/rift-raknet/LICENSE).

About

Fast, seamless Minecraft Bedrock proxy in Rust - no-reconnect server transfers (PocketMine-MP). A WaterdogPE alternative.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors