Protocol

This file is mirrored from the libtw2 documentation and is dual-licensed under MIT or APACHE.

This document describes the low-level Teeworlds protocol up to 0.6.x and in DDNet. The packet headers are described in packet.md.

The Teeworlds protocol is a layer over UDP. It offers connectionless (stateless) messages, and connections. Connections can be established and torn down. Within connections, reliable ("vital") and unreliable ("non-vital") messages can be sent. Unreliable messages might or might not be delivered and might be received in any order. Reliable messages are guaranteed to arrive in-order, and will get re-sent if they're lost, so they will arrive eventually as long as the connection is not closed.

The Teeworlds 0.6 protocol does not offer protection against IP spoofing, neither for connectionless messages nor for the connections themselves. DDNet extends this network protocol to offer protection against IP spoofing for connections.

The Teeworlds protocol defines three different kinds of packets. There are connectionless packets, and two kinds of connection-oriented packets, namely control packets and "normal" packets.

Connectionless packets

Any packet having the flag_connless header flag set is considered connectionless. All other header flags and fields are ignored. After the header, there are 3 bytes of padding. The bits of these first 6 bytes are supposed to be all-ones.

All sizes in bits.

packet_connless:
    [24] packet_header
    [24] padding
    [  ] payload

NOTE: The reference implementation only care about the flag_connless header flag being set and ignores all of the other 47 bits.

Connection-oriented packets

In order to guarantee reliable messages being received in the right order, each reliable message gets assigned a 10-bit sequence number, starting at 0, incremented for each message and wrapping around after 1023 to 0.

All connection-oriented packets have flag_connless set to 0. Each peer keeps track of the highest sequence number such that it has received all reliable messages with sequence numbers smaller or equal to it (note: this is simplified: sequence numbers wrap around, so it's not straightforward to say which sequence numbers are larger and which are smaller). This highest sequence number must be reported in the ack field, it tells the other peer that it can forget about these reliable messages.

If a peer detects that it's missing reliable messages (when receiving higher sequence numbers without the ones in between), it sets flag_resend. This tells the other peer to start re-sending messages starting from the client's current ack.

flag_compression tells us whether the maybe_compressed_payload of the packet (everything after the header) has been compressed using the Huffman compression described in huffman.md. Peers compress the payload and then send whichever of the compressed or uncompressed payload is smaller. Packets with flag_control must not have flag_compression set.

NOTE: The reference implementation still interprets flag_compression for packets with flag_control.

Finally, flag_control determines if the payload is a control packet or a "normal" packet.

packet_connected:
    [24] packet_header
    [  ] maybe_compressed_payload

Control packet

num_chunks should be 0.

There are five different control messages. The first byte of the payload determines the kind of control message. There are five different defined control messages.

keepalive = 0
connect = 1
connectaccept = 2
accept = 3
close = 4

All of these do not have extra data, except for the close control message which can optionally take a nul-terminated UTF-8 string as the close reason.

packet_control:
    [24] packet_header
    [ 8] control_message

packet_control_close:
    [24] packet_header
    [ 8] control_message
    [  ] reason

When a connection is being established, there's a clear difference between client and server. The client starts by sending a connect control message. When the server receives a connect, it responds with a connectaccept. When the client sees a connectaccept, it sends an accept (which is ignored by the server) and considers the connection active. When the server receives its first normal packet, it also considers the connection active.

Either party may send a close at any point (as a response to connect, connectaccept, during an active connection or even just upon receiving a packet belonging to an unknown connection).

Normal packets

Normal packets carry reliable and unreliable messages ("chunks").

Each message is prepended a chunk_header_nonvital or chunk_header_vital, depending on whether flag_vital is set. flag_resend indicates whether the message was sent in response to a flag_request_resend of a packet header. size is the size of the message excluding the header. sequence indicates the 10-bit sequence number of a reliable message.

Then all of the messages are concatenated (up to a maximum packet size of 1400 bytes), num_chunks specifies the number of these messages.

NOTE: The reference implementation ignores num_chunks and simply reads chunks from the payload until it reaches the end.

DDNet token extension

In order to secure the protocol against IP spoofing. It introduces a 4-byte token that needs to be included as part of every conection-oriented message. The server decides the token, usually based on IP address of the client, so that it doesn't need to save anything before the client IP address is validated.

There are two special tokens. The first one is the all-zero token which is used by the reference implementation for internal tracking and should not be used. The other is the all-ones token which is used before the actual token is known, i.e. in the connect control message.

The token is simply appended to each of the connection-oriented packets, before compression takes place. This means that the receiver needs to decompress the packet before it can validate the token. This is a design flaw, because it requires computational overhead before a packet can be validated.

packet_control_ddnet:
    [24] packet_header
    [ 8] control_message
    [32] token

packet_control_close_ddnet:
    [24] packet_header
    [ 8] control_message
    [  ] close_reason
    [32] token

In the connect packet, since the token isn't known at that time (the server decides on which token to use), it is set to the all-ones token. The connect and connectaccept packets are additionally special in the sense that they are used to signal support for the DDNet token extension.

packet_control_connect_connectaccept_ddnet:
    [24] packet_header
    [ 8] control_message
    [32] token_magic
    [32] token

The token_magic is the ASCII string TKEN, in hex 54 4b 45 4e.

NOTE: The DDNet reference implementation unfortunately uses the accept message to actually let a client connect. Since that one is not re-sent, it means that a single lost packet can cause the connection establishment to get stuck.