MCBE
OverviewTransportRakNet

RakNet

Reliable UDP transport on port 19132 (IPv4) / 19133 (IPv6), RakNet protocol version 11. Still the transport for direct IP connections in 1.26.51.

offline-magic
MAGIC = 00 ff ff 00 fe fe fe fe fd fd fd fd 12 34 56 78
// 16 bytes, present in every unconnected/offline message

Basics

RakNet carries everything before login as plain UDP datagrams and everything after inside reliable frame sets. It provides no encryption of its own — game-layer encryption (AES-256-CTR after the handshake) is applied to the payload of each batch, not to RakNet headers. Multi-byte integers in the offline handshake are big-endian; the 3-byte counters inside frames and ACKs are little-endian. IPv4 addresses are written with each octet bitwise-NOT'd.

address encoding
IPv4: 04 <NOT(ip[0])> <NOT(ip[1])> <NOT(ip[2])> <NOT(ip[3])> <port uint16 BE>   // 7 bytes
IPv6: 06 <AF_INET6=23 uint16 LE> <port uint16 BE> 00 00 00 00 <16-byte ip> 00 00 00 00  // 29 bytes

Offline handshake

sequence
C → S  0x05 OpenConnectionRequest1   // MTU discovery via padding
S → C  0x06 OpenConnectionReply1     // server GUID, security cookie, MTU
C → S  0x07 OpenConnectionRequest2   // cookie, server address, MTU, client GUID
S → C  0x08 OpenConnectionReply2
--- everything below now rides inside frame sets ---
C → S  0x09 ConnectionRequest
S → C  0x10 ConnectionRequestAccepted
C → S  0x13 NewIncomingConnection
C ⇄ S  0x00/0x03 ConnectedPing/Pong keepalives
C ⇄ S  0xFE Game packet batches

MTU discovery probes 1492 first, then 1200, then 576 — each request is retried 4 times, 500 ms apart. OpenConnectionRequest1 pads itself with zeros to the probed MTU minus 28 bytes (IP + UDP header), and the server derives the MTU from the datagram size. Modern implementations send a security cookie in OpenConnectionReply1: a CRC32 of (salt ‖ port ‖ IP) that must be echoed in OpenConnectionRequest2, with the salt rotating every 2 seconds. Vanilla clients always provide a negative client GUID.

open-connection-request-1
uint8    id (= 0x05)
bytes    magic (16)
uint8    protocol version (= 11)
bytes    zero padding ... total datagram length = probed MTU - 28
open-connection-reply-1
uint8    id (= 0x06)
bytes    magic (16)
int64BE  server GUID
uint8    security (0 or 1)
uint32BE cookie        if security
uint16BE mtu
open-connection-request-2
uint8    id (= 0x07)
bytes    magic (16)
uint32BE cookie + 1 skipped byte   if secure
address  server address (see encoding above)
uint16BE mtu
int64BE  client GUID
open-connection-reply-2
uint8    id (= 0x08)
bytes    magic (16)
int64BE  server GUID
address  client address
uint16BE mtu
uint8    doSecurity
connection-request / accepted
0x09 ConnectionRequest:      int64BE clientGUID · int64BE requestTime · uint8 secure
0x10 ConnectionRequestAccepted:
       address clientAddress · uint16BE systemIndex
       address[20] systemAddresses          // NOT-encoded IPv4 zeroes for unused slots
       int64BE pingTime · int64BE pongTime

A client answering a ping with a different protocol version receives 0x19 IncompatibleProtocolVersion (26 bytes: server protocol byte, magic, server GUID).

Message IDs

IDNameFields
0x00ConnectedPingTime (int64 BE) — keepalive, answered with ConnectedPong
0x01UnconnectedPingTime (int64 BE), Magic, Client GUID — server discovery
0x02UnconnectedPingOpenConnectionsHandled like UnconnectedPing
0x03ConnectedPongPing time, pong time (int64 BE)
0x04DetectLostConnectionsKeepalive variant; answered with ConnectedPing
0x05OpenConnectionRequest1Magic, protocol version (11), MTU padding
0x06OpenConnectionReply1Magic, server GUID, security flag (+cookie), MTU
0x07OpenConnectionRequest2Magic, cookie (+1 byte) if secure, server address, MTU, client GUID
0x08OpenConnectionReply2Magic, server GUID, client address, MTU, security flag
0x09ConnectionRequestClient GUID, request time, security byte (reliable)
0x10ConnectionRequestAcceptedClient address, system index, 20 system addresses, ping/pong times
0x13NewIncomingConnectionServer address, 20 system addresses, ping/pong times
0x15DisconnectNotificationSent on graceful close
0x19IncompatibleProtocolVersionServer protocol byte, Magic, server GUID
0x1CUnconnectedPongTime, server GUID, Magic, MOTD string (uint16 BE length)
0x80–0xFEDatagram (frame set)Flag byte 0x84, sequence number (uint24 LE), frames
0xA0NACKRecord count (uint16 BE) + ranges (3-byte LE pairs)
0xC0ACKRecord count (uint16 BE) + singles/ranges (3-byte LE)
0xFEGame Packet batchBedrock batch wrapper: compression marker + packets

Frames & reliability

Connected data is sent in datagrams (frame sets): a flag byte 0x84, a 3-byte little-endian sequence number, then one or more frames. Each frame declares a reliability and may be split into fragments. go-raknet uses five reliability classes; every game packet travels asReliableOrdered.

datagram
uint8    flags = 0x84        // datagram | needsBAndAS
uint24LE sequence number
frame[]  frames              // up to MTU - 28 bytes of content each
frame
uint8    flags          // reliability << 5 | split << 4
uint16BE bitLength      // content length in bits
uint24LE messageIndex   if reliable (2,3,4)
uint24LE sequenceIndex  if sequenced (1,4)
uint24LE orderIndex     if ordered/sequenced (1,3,4)
uint8    orderChannel   always 0 in bedrock
uint32BE splitCount     if split
uint16BE splitId        if split
uint32BE splitIndex     if split
bytes    content
ValueReliabilityBehaviour
0UnreliableFire and forget, no extras
1UnreliableSequencedDrops older packets, sequenceIndex present
2ReliableResent until ACKed, messageIndex present
3ReliableOrderedReliable + orderIndex/orderChannel (Bedrock's choice)
4ReliableSequencedReliable + sequenceIndex + ordering

Split frames reassemble by splitId; servers cap splits at 512 pieces with 16 concurrent split messages. Frame overhead is 14 bytes (plus 10 when split), so the maximum frame content is MTU − 28 − 14.

ACK / NACK

ACK (0xC0) and NACK (0xA0) share one layout: a uint16 BE record count, then records — a 0x01 type byte with a single 3-byte LE sequence number, or a 0x00 type byte with min and max 3-byte LE numbers. ACKs confirm received datagrams, NACKs request retransmission of gaps. Records are capped so a reply fits one MTU; servers expand at most 8192 records.

ack
uint8    flags = 0xC0       // 0xA0 for NACK
uint16BE recordCount
record[] records
  0x01 uint24LE seq                  // single
  0x00 uint24LE min · uint24LE max   // inclusive range

0xFE game packets

Minecraft rides inside frames whose content starts with the batch header 0xFE. Everything after that byte is optional ciphertext, then the compression marker, then a stream of length-prefixed packets. RakNet itself never sees individual game packets.

batch (unencrypted)
0xFE                                // batch header
[8-byte checksum]                   // only when encrypted (see Encryption page)
uint8  compression marker           // 0x00 zlib, 0x01 snappy, 0xFF uncompressed
batch: [varuint32 length · packet]* // packet = varuint32 header + body
                                    // header = id | sender<<10 | target<<12

The inner header packs the packet ID (10 bits) with sender and target sub-client indices (2 bits each at bits 10–11 and 12–13) for split-screen support. A batch holds at most 812 packets and decompresses to at most 16 MiB.

Timing & limits

ConstantValue
RakNet protocol version11 (rejected with IncompatibleProtocolVersion otherwise)
MTU probe sequence1492 → 1200 → 576, 4 attempts each, 500 ms apart
Minimum supported MTU576 (probes below 400 clamped)
KeepaliveConnectedPing every 500 ms
Connection timeout5 s + 2 × RTT without activity
Retransmit thresholdRTT + RTT/2 (RTT smoothed over 5 s, default 50 ms)
Tick rate100 ms (ACK flush every tick, resend check every 300 ms)
Receive window / ordered queue2048 entries (enforced server-side)
Handshake timeouts10 s dial, 5 s ping, 10 s half-open grace
CloseDisconnectNotification (0x15) after pending resends ACKed