Mumble protocol, as implemented by Mutter
Sources: mumble-voip/mumble (src/Mumble.proto, src/MumbleUDP.proto, src/MumbleProtocol.h,
src/crypto/CryptStateOCB2.cpp, src/tests/TestCrypt/TestCrypt.cpp, docs/dev/network-protocol/*.md).
Control channel (TCP + TLS)
- Port 64738 by default, same port for UDP.
- TLS 1.2+, server certificate usually self-signed. Client certificate optional but is the identity used for registration; servers key users by the certificate hash.
- Framing:
uint16 type+uint32 length, both big-endian, thenlengthbytes of protobuf. - Type IDs are the message order in
Mumble.proto:
| ID | Message | ID | Message | ID | Message |
|---|---|---|---|---|---|
| 0 | Version | 9 | UserState | 18 | UserList |
| 1 | UDPTunnel | 10 | BanList | 19 | VoiceTarget |
| 2 | Authenticate | 11 | TextMessage | 20 | PermissionQuery |
| 3 | Ping | 12 | PermissionDenied | 21 | CodecVersion |
| 4 | Reject | 13 | ACL | 22 | UserStats |
| 5 | ServerSync | 14 | QueryUsers | 23 | RequestBlob |
| 6 | ChannelRemove | 15 | CryptSetup | 24 | ServerConfig |
| 7 | ChannelState | 16 | ContextActionModify | 25 | SuggestConfig |
| 8 | UserRemove | 17 | ContextAction | 26 | PluginDataTransmission |
- Type 1 (UDPTunnel) is not protobuf: the payload is a raw voice packet, unencrypted.
- Field numbers for every message are mirrored in
MumbleProtocol/Messages.swift.
Handshake
- TLS connect.
- Client →
Version{version_v1, version_v2, release, os, os_version}. - Client →
Authenticate{username, password?, tokens[], opus: true, client_type: 0}. - Server →
Version,CryptSetup{key, client_nonce, server_nonce},CodecVersion, manyChannelState(channels first without links, then links),PermissionQuery, manyUserState, thenServerSync{session, max_bandwidth, welcome_text, permissions}, thenServerConfig,SuggestConfig. Reject{type, reason}instead ofServerSyncmeans failure.- After sync: client sends
Pingon TCP every 5 s (with UDP stats), and an encrypted UDP ping every 5 s. Server drops clients silent for ~30 s.
Version encodings:
- v1 (
uint32):major << 16 | minor << 8 | patch, minor/patch saturate at 255. - v2 (
uint64):major << 48 | minor << 32 | patch << 16. - Prefer v2 when non-zero.
Voice (UDP)
Every UDP datagram is OCB2-AES128 encrypted: [iv byte][tag0][tag1][tag2][ciphertext].
Encrypt key = CryptSetup.key; client encrypts with client_nonce as its IV, decrypts with
server_nonce. The IV is incremented (little-endian across 16 bytes) before each encrypt.
The plaintext format depends on the server's version:
Legacy (server < 1.5.0)
- Byte 0:
type << 5 | target. Types: 0 CELT α, 1 Ping, 2 Speex, 3 CELT β, 4 Opus. Only Opus is implemented. Target: 0 normal, 1–30 whisper targets, 31 loopback. In server→client packets the low 5 bits mean context: 0 normal, 1 shout, 2 whisper, 3 listener. - Audio:
varint session(server→client only),varint sequence,varint lengthwhere bit0x2000is the terminator flag and the low 13 bits are the Opus payload length, Opus bytes, optional 3 little-endian floats of position. - Ping: byte
0x20, thenvarint timestamp. - Mumble varint prefixes:
0xxxxxxx7-bit,10xxxxxx+1 14-bit,110xxxxx+2 21-bit,1110xxxx+3 28-bit,111100__+4 32-bit,111101__+8 64-bit,111110__+ varint = negated,111111xx=~xx(−1…−4).
Protobuf (server ≥ 1.5.0)
- Byte 0:
0= Audio,1= Ping. Then aMumbleUDP.Audio/MumbleUDP.Pingprotobuf. - Audio fields: 1 target / 2 context (oneof), 3 sender_session, 4 frame_number, 5 opus_data, 6 positional_data (repeated float, may be packed), 7 volume_adjustment, 16 is_terminator.
- Ping fields: 1 timestamp, 2 request_extended_information, 3 server_version_v2, 4 user_count, 5 max_user_count, 6 max_bandwidth_per_user.
Tunnel fallback
If no encrypted UDP ping reply arrives within ~10 s, voice is sent as UDPTunnel frames on TCP
(same plaintext format, no encryption). Return to UDP as soon as pings flow again. If decrypts
stall for 5 s while UDP is up, send an empty CryptSetup to ask for a fresh server_nonce;
if the server sends an empty CryptSetup, reply with CryptSetup{client_nonce = encrypt IV}.
Server probe (unencrypted)
Request: 00 00 00 00 + 8-byte identifier. Reply: uint32 version_v1, identifier,
uint32 users, uint32 max_users, uint32 bandwidth; all big-endian. Used for the server list.
OCB2-AES128
Port of CryptStateOCB2.cpp. S2 doubles in GF(2^128) (shift left across 16 bytes, xor 0x87 on
carry), S3 = S2 ^ identity. Tail block: pad = E(len·8 in the last 4 bytes ⊕ delta); checksum
includes the padded tail. Tag = E(S3(delta) ⊕ checksum); first 3 bytes go on the wire.
Decrypt nonce recovery (from upstream): in-order if iv[0]+1 == byte; otherwise compute the
signed byte difference; -30 < diff < 0 → late packet (decrypt with adjusted IV, then restore);
diff > 0 → lost packets, advance; anything else → drop. A replay table history[iv[0]] = iv[1]
rejects repeats.
XEX* countermeasure (2019): on encrypt, if the second-to-last block is zero in bytes 0–14, flip bit 0 of that block and of the checksum. On decrypt, fail if the padded last block equals delta in bytes 0–14.
Test vectors (key = nonce = 00 01 … 0f):
- empty message → tag
BF3108130773AD5EC70EC69E7875A7B0 - plaintext
00…27(40 bytes) → ciphertextF75D6BC8B4DC8D66B836A2B08B32A6369F1CD3C5228D79FD6C267F5F6AA7B231C7DFB9D59951AE9C, tag9DB0CDF880F73E3E10D4EB3217766688
Permissions bits
write 0x1, traverse 0x2, enter 0x4, speak 0x8, muteDeafen 0x10, move 0x20, makeChannel 0x40, linkChannel 0x80, whisper 0x100, textMessage 0x200, makeTempChannel 0x400, listen 0x800, kick 0x10000, ban 0x20000, register 0x40000, selfRegister 0x80000, resetUserContent 0x100000.