Teeworlds 0.7

Control messages 0.7

There are three types of messages with overlapping message ids:
system messages, game messages and control messages
This list is only covering control messages.

Control messages have a simpler layout than game or system messages. They are always sent as a single packet. So there is no chunking and flushing. There is also no message header just its id and payload. Note that 0.6.5 introduced security tokens for control messages. Here the control msg send method used to send all of them.

                
    void CNetBase::SendControlMsg(const NETADDR *pAddr, TOKEN Token, int Ack, int ControlMsg, const void *pExtra, int ExtraSize)
    {
        CNetPacketConstruct Construct;
        Construct.m_Token = Token;
        Construct.m_Flags = NET_PACKETFLAG_CONTROL;
        Construct.m_Ack = Ack;
        Construct.m_NumChunks = 0;
        Construct.m_DataSize = 1+ExtraSize;
        Construct.m_aChunkData[0] = ControlMsg;
        if(ExtraSize > 0)
            mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize);

        // send the control message
        SendPacket(pAddr, &Construct);
    }
                
            
Note that it shares the same teeworlds packet header so it has the fields NumChunks. But it is set to 0 at all times for control messages. Also control messages are never compressed using huffman compression.


NET_CTRLMSG_KEEPALIVE

Sender: Client
Server
Recipient: Client
Server
Message ID:0
Response to: Sent after 250ms of silence
Expected response: None
Argument name Type Note
None
If no packets are exchanged for too long the connection times out. So this messages whole purpose to keep the connection alive in case of no packets being exchanged. Which rarley happens in regular healthy connections due to the protocol being very chatty. It is sent automatically if needed in network_conn.cpp which is shared by client and server.
                
    // system.c
    int64 time_freq()
    {
    #if defined(CONF_FAMILY_UNIX)
        return 1000000;
    #elif defined(CONF_FAMILY_WINDOWS)
        int64 t;
        QueryPerformanceFrequency((PLARGE_INTEGER)&t);
        return t;
    #else
        #error not implemented
    #endif
    }

    // network_conn.cpp
    int CNetConnection::Update()
    {
        // [..]

        // send keep alives if nothing has happend for 250ms
        if(State() == NET_CONNSTATE_ONLINE)
        {
            // [..]

            if(time_get()-m_LastSendTime > time_freq())
                SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0);
        }

        // [..]

        return 0;
    }
                
            


NET_CTRLMSG_CONNECT

Sender: Client
Recipient: Server
Message ID:1
Response to: NET_CTRLMSG_TOKEN
Expected response: NET_CTRLMSG_ACCEPT
Argument name Type Note
Response token Raw Response token = client token

The token of the server was already included in the packet header. This field then holds the token of the client it self. So this message is informing what token the server should put in the headers it sends to the client from now on.

Technically the tokens are already exchanged in the first two NET_CTRLMSG_TOKEN messages. So this payload is a bit redundant and not very critical.
Null bytes Raw 508 null bytes to protect against reflection attacks.
The client and server code for sending and receiving this message is in the same shared function CNetConnection::Feed()
                
    int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
    {
        // [..]

        int64 Now = time_get();

        // [..]

        //
        if(pPacket->m_Flags&NET_PACKETFLAG_CONTROL)
        {
            int CtrlMsg = pPacket->m_aChunkData[0];

            if(CtrlMsg == NET_CTRLMSG_CLOSE)
            {
                // [..]
                return 0;
            }
            else
            {
                if(CtrlMsg == NET_CTRLMSG_TOKEN)
                {
                    m_PeerToken = pPacket->m_ResponseToken;

                    if(State() == NET_CONNSTATE_TOKEN)
                    {
                        m_LastRecvTime = Now;
                        m_State = NET_CONNSTATE_CONNECT;
                        SendControlWithToken(NET_CTRLMSG_CONNECT);
                        dbg_msg("connection", "got token, replying, token=%x mytoken=%x", m_PeerToken, m_Token);
                    }
                    else if(Config()->m_Debug)
                        dbg_msg("connection", "got token, token=%x", m_PeerToken);
                }
                else
                {
                    if(State() == NET_CONNSTATE_OFFLINE)
                    {
                        if(CtrlMsg == NET_CTRLMSG_CONNECT)
                        {
                            // send response and init connection
                            TOKEN Token = m_Token;
                            Reset();
                            mem_zero(m_ErrorString, sizeof(m_ErrorString));
                            m_State = NET_CONNSTATE_PENDING;
                            m_PeerAddr = *pAddr;
                            m_PeerToken = pPacket->m_ResponseToken;
                            m_Token = Token;
                            m_LastSendTime = Now;
                            m_LastRecvTime = Now;
                            m_LastUpdateTime = Now;
                            SendControl(NET_CTRLMSG_ACCEPT, 0, 0);
                            if(Config()->m_Debug)
                                dbg_msg("connection", "got connection, sending accept");
                        }
                    }
                    else if(State() == NET_CONNSTATE_CONNECT)
                    {
                        // connection made
                        if(CtrlMsg == NET_CTRLMSG_ACCEPT)
                        {
                            m_LastRecvTime = Now;
                            m_State = NET_CONNSTATE_ONLINE;
                            if(Config()->m_Debug)
                                dbg_msg("connection", "got accept. connection online");
                        }
                    }
                }
            }
        }
        // [..]

        return 1;
    }
                
            


NET_CTRLMSG_ACCEPT

Sender: Server
Recipient: Client
Message ID:2
Response to: NET_CTRLMSG_CONNECT
Expected response: NETMSG_INFO
Argument name Type Note
None
In a ideal connection scenario this is the 4th package being exchanged. After the two token packets and the connection request from the client. The server sends this to accept the connection. Which sets the client state to online. The client is now expected to send his version and password.


NET_CTRLMSG_CLOSE

Sender:Client
Server
Recipient:Client
Server
Message ID:4
Response to: NETMSG_RCON_AUTH (Too many remote console authentication tries) |
NETMSG_RCON_CMD kick, ban, shutdown
Expected response: None
Argument name Type Note
Reason String This argument is optional and can also be left out
This message is used to cleanly terminate connections. This can be sent by client on disconnect or by server on shutdown. The optional parameter reason can be sent by both and will be displayed in the disconnect message or on the disconnect screen.

Sent in the CNetConnection::Disconnect(const char *pReason) method.
                
    void CNetConnection::Disconnect(const char *pReason)
    {
        if(State() == NET_CONNSTATE_OFFLINE)
            return;

        if(m_RemoteClosed == 0)
        {
            if(pReason)
                SendControl(NET_CTRLMSG_CLOSE, pReason, str_length(pReason)+1);
            else
                SendControl(NET_CTRLMSG_CLOSE, 0, 0);

            if(pReason != m_ErrorString)
            {
                if(pReason)
                    str_copy(m_ErrorString, pReason, sizeof(m_ErrorString));
                else
                    m_ErrorString[0] = 0;
            }
        }

        Reset();
    }
                
            
Unpacket in CNetConnection::Feed()
                
    if(CtrlMsg == NET_CTRLMSG_CLOSE)
    {
        if(net_addr_comp(&m_PeerAddr, pAddr) == 0)
        {
            m_State = NET_CONNSTATE_ERROR;
            m_RemoteClosed = 1;

            char aStr[128] = {0};
            if(pPacket->m_DataSize > 1)
            {
                // make sure to sanitize the error string form the other party
                if(pPacket->m_DataSize < 128)
                    str_copy(aStr, (char *)&pPacket->m_aChunkData[1], pPacket->m_DataSize);
                else
                    str_copy(aStr, (char *)&pPacket->m_aChunkData[1], sizeof(aStr));
                str_sanitize_strong(aStr);
            }

            if(!m_BlockCloseMsg)
            {
                // set the error string
                SetError(aStr);
            }

            if(g_Config.m_Debug)
                dbg_msg("connection", "closed reason='%s'", aStr);
        }
        return 0;
    }
                
            
If there is no reason set the payload of the teeworlds packet will only be the byte 0x04. If there is a reason it will be appended as a null terminated string.
                
 NET_CTRLMSG_CLOSE
    |
    | All the rest is reason
    |  |
    v  v
    04 53 65 72 76 65 72 20 73 68 75   .Server shu
    74 64 6f 77 6e 00                  tdown.
                   ^
                   |
                Terminating null byte
                
            


NET_CTRLMSG_TOKEN

Sender:Client
Server
Recipient:Client
Server
Message ID:5
Response to: NET_CTRLMSG_TOKEN
Expected response: Client expects NET_CTRLMSG_TOKEN
Server expects NET_CTRLMSG_CONNECT
Argument name Type Note
Response token Raw Response token = senders token

This field is used by client and server to send their own token to the other party.
Null bytes Raw 508 null bytes to protect against reflection attacks.
This message is the first message sent by client and server. First the client initiates the connection by telling its own token to the server. The packet header contains FF FF FF FF as the server token indicating a empty token. The server then responds with the client token in the packet header and his own token as payload.

Note that the server will drop all token packets that are smaller than 512 bytes. So the client appends a bunch of null bytes at the end. This is to protect against reflection attacks. The servers message does not include any null bytes.

Here a full hexdump of the teeworlds packet being sent by the client. Including not just the token message but also its teeworlds packet header to see where the token is being used:
                
    [TEEWORLDS PACKET HEADER]
    04 00 00 ff ff ff ff                              .......
    ^        \_________/
    |             |
    |       Server token placeholder (token is not known yet)
    |
    0x04 hex
    0001 binary
       ^
       |
    control flag

    [NET_CTRLMSG_TOKEN]
    05 24 05 cd fd                                    .$...
    ^  \_________/
    |       |
    |    Response token
    |    Client token the server should
    |    put in all packet headers from now on
    |
    |
    0x05 -> Unpacker.GetInt() -> 5 -> NET_CTRLMSG_TOKEN

    [NULL BYTES]
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00               ............
                
            
How the server responds with his token
                
    [TEEWORLDS PACKET HEADER]
    04 00 00 24 05 cd fd      ......$
    ^        \_________/
    |            |
    |       Client token from the payload
    |       of the NET_CTRLMSG_TOKEN the client just sent
    |
    0x04 hex
    0001 binary
       ^
       |
    control flag

    [NET_CTRLMSG_TOKEN]
    05 fc 55 3a aa            ..U:.
    ^  \_________/
    |       |
    |   Response token
    |   Server token the client should
    |   put in all packet headers from now on
    |
    0x05 -> Unpacker.GetInt() -> 5 -> NET_CTRLMSG_TOKEN