# BungeeCord Legacy IP Forwarding The original forwarding scheme, introduced by BungeeCord and adopted everywhere. It works by **abusing the handshake's `serverAddress` field** — the proxy appends the player's identity to that string, null-byte delimited, and the offline-mode backend parses it back out. **No cryptography is involved.** ## The mechanism The handshake packet (see [../03-handshake.md](../03-handshake.md)) has a `serverAddress` (a.k.a. *Server Address* / host) string — normally the hostname the client used to connect (e.g. `mc.example.com`). The vanilla server mostly ignores its content. BungeeCord repurposes it: when IP-forwarding is enabled, the proxy **rewrites** that field before opening the backend connection, packing four `\0`-separated segments into it. The backend (a Spigot/Paper server with `settings.bungeecord: true`, or a Fabric server with an equivalent mod) recognizes the extra segments and reads the player's real IP, UUID, and properties out of them — instead of treating the whole string as a hostname. ## Exact wire format The proxy rewrites the handshake host to: ``` realHost \0 clientIP \0 playerUUID(no dashes) \0 texturesPropertiesJSON ``` Where (in order): | Segment | Content | |---|---| | `realHost` | the original handshake host the client sent (e.g. `mc.example.com`), minus any trailing FML marker | | `clientIP` | the player's real socket IP, sanitized (brackets stripped from IPv6, scope id removed) | | `playerUUID` | the player's UUID **with dashes removed** (32 hex chars) | | `texturesPropertiesJSON` | a JSON array of the login profile's game-profile **properties** (the Mojang-signed `textures` skin/cape entry). **Omitted entirely** (along with its leading `\0`) if the profile has no properties — i.e. the player connected in offline/cracked mode upstream. | This is exactly what BungeeCord writes in `ServerConnector.connected()`: > ```java > String newHost = copiedHandshake.getHost() + "\00" + AddressUtil.sanitizeAddress( user.getAddress() ) + "\00" + user.getUUID(); > > LoginResult profile = user.getPendingConnection().getLoginProfile(); > if ( profile != null && profile.getProperties() != null && profile.getProperties().length > 0 ) > { > newHost += "\00" + LoginResult.GSON.toJson( profile.getProperties() ); > } > copiedHandshake.setHost( newHost ); > ``` > > — `BungeeCord/proxy/src/main/java/net/md_5/bungee/ServerConnector.java:116-123` (BungeeCord writes the null byte as the Java octal escape `"\00"`, i.e. a single `U+0000`.) Note the **UUID has no dashes** — `user.getUUID()` returns the undashed form. The properties JSON is the serialized array of `{name, value, signature}` objects; for an online player it's the single Mojang-signed `textures` property. Velocity, when configured in `legacy` mode, builds the **same** string — and its source documents the format verbatim: > ```java > // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, > // separated by \0 (the null byte). In order, you send the original host, the player's IP, their > // UUID (undashed), and if you are in online-mode, their login properties (from Mojang). > final StringBuilder data = new StringBuilder() > .append(serverAddress).append(LEGACY_SEPARATOR) > .append(playerAddress).append(LEGACY_SEPARATOR) > .append(profile.getUndashedId()).append(LEGACY_SEPARATOR); > GENERAL_GSON.toJson(profile.getProperties(), data); > ``` > > — `Velocity/proxy/src/main/java/com/velocitypowered/proxy/connection/PlayerDataForwarding.java:160-172` (`LEGACY_SEPARATOR = '\0'`, defined at `:50`) ## How the backend parses it The receiving side splits the host on `\0`. BungeeCord's *own* handshake handler (which is what a downstream BungeeCord-as-backend, or a Spigot `bungeecord:true` server emulating the same logic, does) splits the host and keeps the tail: > ```java > if ( handshake.getHost().contains( "\0" ) ) > { > String[] split = handshake.getHost().split( "\0", 2 ); > handshake.setHost( split[0] ); > extraDataInHandshake = "\0" + split[1]; > } > ``` > > — `BungeeCord/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java:355-360` A Spigot/Paper backend with bungeecord forwarding enabled does the analogous thing: it splits the host into `[host, ip, uuid, properties]`, sets the player's address to `ip`, the UUID to the dash-inserted form of `uuid`, and the game-profile properties to the parsed JSON. ## Sequence ```mermaid sequenceDiagram autonumber actor C as "Client (player)" participant P as "Proxy (BungeeCord, ip_forward=true)" participant B as "Backend (Spigot bungeecord=true, offline-mode)" C->>P: "Handshake host=mc.example.com" C->>P: "LoginStart (username)" Note over P: "online-mode auth vs Mojang (hasJoined)" Note over P: "rewrite host -> realHost\\0clientIP\\0uuidNoDashes\\0propsJSON" P->>B: "Handshake host='realHost\\0IP\\0UUID\\0props'" P->>B: "LoginStart (username, rewriteId)" Note over B: "split host on \\0; trust IP/UUID/props as-is" B-->>C: "LoginSuccess (via proxy relay) -> play" ``` ## Security: none There is **no signature, no secret, no verification**. The backend trusts the `\0`-delimited string completely. If an attacker can reach the backend's port, they simply send a handshake with a hand-crafted `host\0ip\0uuid\0props` string and connect as **any player they like**, with any UUID and any skin. The *only* defense for bare legacy forwarding is the network: **firewall the backend so only the proxy can connect** (bind to localhost / a private interface; drop everything else). This is the central weakness that [BungeeGuard](bungeeguard.md) (adds a secret token to the properties) and [Velocity modern forwarding](velocity-modern.md) (HMAC-signs the whole payload) exist to fix. Velocity even warns the operator when a legacy backend closes the connection — almost always a misconfigured `bungeecord: true`: > *"This is usually because the remote server does not have BungeeCord IP forwarding correctly enabled."* — `Velocity/.../backend/LoginSessionHandler.java:205-212` ## Forge note If the client is on Forge, FML appends its own `\0FML\0` (or newer `\0FML2\0` / `\0FML3\0`) marker to the handshake host. The proxy must split that off **before** injecting the forwarding segments and re-append it after, or the marker collides with the forwarding `\0` delimiters. BungeeCord handles this by stashing everything from the first `\0` as `extraDataInHandshake` (`InitialHandler.java:355-360`) and restoring it only when IP forwarding is *off* (`ServerConnector.java:124-128`). See [forge-fml.md](forge-fml.md). --- **Sources** - `BungeeCord/proxy/src/main/java/net/md_5/bungee/ServerConnector.java:116-123` — the WRITE of `host\0ip\0uuid\0props`. - `BungeeCord/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java:355-360` — the split/parse of the host on `\0`. - `Velocity/proxy/src/main/java/com/velocitypowered/proxy/connection/PlayerDataForwarding.java:50` (`LEGACY_SEPARATOR`), `:154-173` (`createLegacyForwardingAddress`) — Velocity building the identical string, with the format documented in-comment. - `Velocity/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java:205-212` — legacy-misconfiguration diagnostic.