Teeworlds 0.6

Control messages 0.6.5

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(NETSOCKET Socket, NETADDR *pAddr, int Ack, bool UseToken, unsigned Token, int ControlMsg, const void *pExtra, int ExtraSize)
    {
        CNetPacketConstruct Construct;
        Construct.m_Flags = NET_PACKETFLAG_CONTROL|(UseToken?NET_PACKETFLAG_TOKEN:0);
        Construct.m_Ack = Ack;
        Construct.m_NumChunks = 0;
        Construct.m_Token = Token;
        Construct.m_DataSize = 1+ExtraSize;
        Construct.m_aChunkData[0] = ControlMsg;
        mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize);

        // send the control message
        CNetBase::SendPacket(Socket, 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()
    {
        int64 Now = time_get();
        int64 Freq = time_freq();

        // [..]

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

            if(Now-m_LastSendTime > Freq)
                SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0);
        }
        // [..]

        return 0;
    }
                
            


NET_CTRLMSG_CONNECT

Sender: Client
Recipient: Server
Message ID:1
Response to: TODO
Expected response: TODO
Argument name Type Note
Null Raw 4 null bytes
Was added in 0.6.5! So 0.6.4 and earlier do not send any payload!
Client Token Raw Fixed size 4 byte random security token.
Was added in 0.6.5! So 0.6.4 and earlier do not send a token!
Null Raw 504 null bytes filling the total payload size to be 512 plus the control message id.
Was added in 0.6.5! So 0.6.4 and earlier do not send null bytes!
Since this is the very first packet that is sent by the client to initiate the connection the payload has to be over 512 bytes otherwise the server will simply drop it. This is for security purpose to avoid reflection attacks. Sent via the SendConnect() wrapper.
                
    void CNetConnection::SendConnect()
    {
        unsigned char aConnect[512] = {0};
        uint32_to_be(&aConnect[4], m_Token);
        SendControl(NET_CTRLMSG_CONNECT, aConnect, sizeof(aConnect));
    }
                
            
Here a sample payload of a NET_CTRLMSG_CONNECT sent over the network. Note this is only the payload. There has to be a teeworlds packet header in front of it.
                
                         Token
                          / \
                         /   \
                        /     \
   NET_CTRLMSG_CONNECT /       \
       v              /         \
       01 00 00 00 00 80 45 7f 2e 00 00 00 00 00 00    ......E........
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
    00 00                                             ..

                
            
Unpacked by the server in CNetServer::Recv()
                
    if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
    {
        if(ClientID != -1)
        {
            continue; // silent ignore.. we got this client already
        }
        if(m_RecvUnpacker.m_Data.m_DataSize >= 1+512)
        {
            unsigned MyToken = GetToken(Addr);
            unsigned char aConnectAccept[4];
            uint32_to_be(&aConnectAccept[0], MyToken);
            CNetBase::SendControlMsg(m_Socket, &Addr, 0, true, Token, NET_CTRLMSG_CONNECTACCEPT, aConnectAccept, sizeof(aConnectAccept));
            if(g_Config.m_Debug)
            {
                dbg_msg("netserver", "got connect, sending connect+accept challenge");
            }
        }
        // [..]
                
            


NET_CTRLMSG_CONNECTACCEPT

Sender: Server
Recipient: Client
Message ID:2
Response to: TODO
Expected response: TODO
Argument name Type Note
Server Token Raw Fixed size 4 byte random security token
Sent by the server in CNetServer::Recv()
                
    if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
    {
        if(ClientID != -1)
        {
            continue; // silent ignore.. we got this client already
        }
        if(m_RecvUnpacker.m_Data.m_DataSize >= 1+512)
        {
            unsigned MyToken = GetToken(Addr);
            unsigned char aConnectAccept[4];
            uint32_to_be(&aConnectAccept[0], MyToken);
            CNetBase::SendControlMsg(m_Socket, &Addr, 0, true, Token, NET_CTRLMSG_CONNECTACCEPT, aConnectAccept, sizeof(aConnectAccept));
            if(g_Config.m_Debug)
            {
                dbg_msg("netserver", "got connect, sending connect+accept challenge");
            }
        }
        // [..]
                
            
Unpacked by the client in CNetConnection::Feed()
                
    if(State() == NET_CONNSTATE_CONNECT)
    {
        // connection made
        if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT)
        {
            if(pPacket->m_Flags&NET_PACKETFLAG_TOKEN)
            {
                if(pPacket->m_DataSize < 1+4)
                {
                    if(g_Config.m_Debug)
                    {
                        dbg_msg("connection", "got short connect+accept, size=%d", pPacket->m_DataSize);
                    }
                    return 1;
                }
                m_Token = uint32_from_be(&pPacket->m_aChunkData[1]);
            }
            else
            {
                m_UseToken = false;
            }
            m_LastRecvTime = Now;
            m_State = NET_CONNSTATE_ONLINE;
            if(g_Config.m_Debug)
                dbg_msg("connection", "got connect+accept, sending accept. connection online");
        }
    }
                
            
Here a sample payload of a NET_CTRLMSG_CONNECTACCEPT sent over the network. Note this is only the payload. There has to be a teeworlds packet header in front of it.
                
    NET_CTRLMSG_CONNECTACCEPT
        |
        |       token
        |   _____|___
        v  /         \
        02 5e 40 3a b8   .^@:.
                
            


NET_CTRLMSG_ACCEPT

Sender: Client
Recipient: Server
Message ID:3
Response to: NET_CTRLMSG_CONNECTACCEPT
Expected response: TODO
Argument name Type Note
None
Got remove in the 0.6.5 token patch.
Because 0.6.5 released while 0.7 was already in full development it also got removed in 0.7 in a seperate commit.
The client uses it to ack the servers accept. The first three messages sent when a connection is established are the following:
                
    client -> server NET_CTRLMSG_CONNECT
    server -> client NET_CTRLMSG_ACCEPTCONNECT
    client -> server NET_CTRLMSG_ACCEPT (ack servers accept)
                
            


NET_CTRLMSG_CLOSE

Sender: Client
Server
Recipient: Client
Server
Message ID:4
Response to: TODO
Expected response: TODO
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