minecraft_protocol: foundation + per-version protocol docs 1.7.10->26.2

8 topical docs (overview, data types, lifecycle, handshake, status/ping,
login+encryption, configuration, version-differences) + proxy-forwarding set
+ 16 per-version release-line docs, sourced from minecraft.wiki, ViaVersion
(source + commits), minecraft-data, node-minecraft-protocol, Velocity, BungeeCord.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude-timemachine
2026-06-19 14:15:32 +02:00
commit d73c1c9537
35 changed files with 8894 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
# Online vs Offline Mode
The `online-mode` server property is the root of every forwarding decision. Understand it first; the four forwarding modes are all answers to the problem offline mode creates.
## What `online-mode` controls
`online-mode=true` (the default in `server.properties`) means: **after** the login handshake, the server verifies the connecting player against Mojang's session servers. It does **not** by itself control whether the connection is encrypted.
The login sequence (see [../05-login-encryption.md](../05-login-encryption.md) for the full packet flow) is, in both modes:
1. Client → `LoginStart` (username, and on 1.19+ a profile public key / on 1.20.2+ the profile UUID).
2. Server → `Encryption Request` (server-id string, server's RSA public key, verify token).
3. Client → `Encryption Response` (the AES shared secret + verify token, both RSA-encrypted with the server's public key).
4. Both sides switch to **AES/CFB8** encryption using the shared secret.
Steps 24 — the encryption negotiation — **happen regardless of online/offline mode** whenever the vanilla server requests them. What online mode adds is one extra check between steps 3 and 4 conceptually:
- **Online mode**: the server computes the *server-id hash* (SHA-1 over the server-id string + shared secret + server public key) and calls Mojang's session endpoint **`hasJoined`**:
`https://sessionserver.mojang.com/session/minecraft/hasJoined?username=<name>&serverId=<hash>`.
Mojang confirms that this account really just authenticated with that server-id (the client side calls the matching `join` endpoint). The response carries the player's **real account UUID** and **properties** (the `textures` property — skin & cape, signed by Mojang). If `hasJoined` returns nothing, the login is rejected.
- **Offline mode**: the server **skips the `hasJoined` call entirely**. No Mojang verification. The server trusts the username from `LoginStart` as-is. (Vanilla offline servers also skip the encryption step; the point is that *no identity proof* is required.)
So the difference that matters for forwarding is: **offline mode does not call `hasJoined`, so it has no proof the username is real, and it gets no real UUID or skin from Mojang.** It must invent both.
## UUID derivation
How the player's UUID is determined differs by mode:
- **Online mode**: the UUID is the player's **real Mojang account UUID**, returned by the `hasJoined` response. It's a stable, account-bound, "version 4"-style identifier assigned by Mojang.
- **Offline mode**: the server **derives** a UUID deterministically from the username. The algorithm is a **name-based (version 3 / MD5) UUID** over the bytes of the ASCII string `"OfflinePlayer:" + username`:
```java
UUID offlineId = UUID.nameUUIDFromBytes(
("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8));
```
`java.util.UUID.nameUUIDFromBytes` produces an **RFC-4122 version 3** UUID — it MD5-hashes the input bytes and stamps the version/variant bits. The same username always yields the same offline UUID, on every server, forever. (Note: no namespace UUID is prepended — it's a plain MD5 of just those bytes, which is why this is "version-3-like" rather than a strictly RFC-compliant namespaced v3.)
This is confirmed directly in BungeeCord's source — `InitialHandler.finish()`:
> `offlineId = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( StandardCharsets.UTF_8 ) );`
>
> — `BungeeCord/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java:560`
and the [minecraft.wiki](https://minecraft.wiki/w/Universally_unique_identifier) describes it the same way: *"the unique identifier chosen for offline mode players is UUID version 3, generated from the MD5 hash of `OfflinePlayer:<username>`."*
The practical consequence: a premium player has **different** UUIDs online vs offline. This is exactly why forwarding must carry the *real* (online) UUID to the backend — otherwise the offline backend would assign the player a different identity than the one their account/inventory/permissions are keyed to.
## Security model
> **An offline-mode backend with a reachable port is wide open.**
Because an offline server does no `hasJoined` check, it accepts **whatever username the connecting party sends in `LoginStart`** and derives the UUID from it. There is no proof. Anyone who can reach the port can connect as `Notch`, as an admin, as any player — and the offline server will hand them that player's UUID, inventory, and op level.
In a proxy setup the player's identity is established **at the proxy** (which runs online mode and does the real `hasJoined`). The backends are deliberately offline so the proxy can move players. That is the entire reason forwarding + hardening exist:
1. **Forwarding** gives the backend the player's *real* IP / UUID / properties instead of the bogus offline-derived ones — see [bungeecord-legacy.md](bungeecord-legacy.md) and [velocity-modern.md](velocity-modern.md).
2. **Hardening** stops an attacker from connecting *directly* to the offline backend and forging that same identity:
- **Firewall** the backend so only the proxy's address can connect (the *only* defense for bare legacy / none).
- **BungeeGuard** — a shared secret token the backend checks ([bungeeguard.md](bungeeguard.md)).
- **Modern/Velocity** — an HMAC the backend verifies, so forged identities are cryptographically rejected ([velocity-modern.md](velocity-modern.md)).
Bind your backends to a private interface and trust nothing that arrives without one of these proofs.
---
**Sources**
- `BungeeCord/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java:560` — offline UUID = `nameUUIDFromBytes("OfflinePlayer:"+name)`; `:135` `onlineMode`; `:595` `isOnlineMode()` gating the auth call.
- [minecraft.wiki — Universally unique identifier](https://minecraft.wiki/w/Universally_unique_identifier) — version-3 MD5 `OfflinePlayer:<username>` derivation.
- [minecraft.wiki — Java Edition protocol (login + encryption)](https://minecraft.wiki/w/Java_Edition_protocol) — Encryption Request/Response, server-id hash, `hasJoined`.
- See also [../05-login-encryption.md](../05-login-encryption.md) for the encryption packet detail.