Permalink
Browse files

Add tokens to the protocol

Tokens are added to the protocol in order to prevent IP spoofing attacks
from being successful.

A new flag has been added to the packet header, in previously unused
space that has incorrectly been interpreted as part of the `ack` field
(which needs to be 10 bits wide but 12 bits were used).

The flags are now, in the first byte, from highest to lowest:

- Compression
- Resend requested
- Connectionless
- Control
- Token

If the token flag is present, the previously 3 bytes header is extended
by 4 bytes that contain the token[1]. This token was added in order to
prevent IP spoofing to be successful.

The connection setup now works as follows:

The client (connection initiator) sends a `NET_CTRLMSG_CONNECT` message
with a payload of at least 512 bytes, without the token flag. Bytes 5 to
8 of the payload contain the token that the server needs to play back to
the client.

The token flag isn't sent in order to maintain compatibility with
servers that do not support the flag, they would interpret it as part of
the `ack` field and drop the packet otherwise.

The size requirement is in place to make the `NET_CTRLMSG_CONNECT`
message useless for reflection attacks.

The server (connection acceptor) sends a `NET_CTRLMSG_CONNECTACCEPT`
message with the token field set to the client's token, and the first
four payload bytes being the token for the established connection.

The server does not save any data about this connection request,
instead, it combines the client's IP together with a frequently rotated
secret in order to obtain the connection token. This prevents the denial
of service attack of sending a lot of `NET_CTRLMSG_CONNECT` packets to
the server.

The client now sends its first protocol message in a packet containing a
token, if the server receives such a message, it allocates a client slot
for the new connection, or sends a close message to inform the client
that no slot is available for it.

For backward compatibility, if `sv_allow_old_clients` is set to 1, the
client continues to accept `NET_CTRLMSG_CONNECTACCEPT` messages without
the token flag.

This removes the `NET_CONNSTATE_PENDING` connection state (as that's
only implicitly tracked now) and the `NET_CTRLMSG_ACCEPT` (as that
message is unused on the other side and is unreliable anyway).
  • Loading branch information...
heinrich5991 committed Sep 27, 2018
1 parent 480de5c commit a263185571903ead01f6b351a91ea219ac9d215f
@@ -1294,6 +1294,40 @@ int str_utf8_encode(char *ptr, int chr);
*/
int str_utf8_check(const char *str);
/*
Function: uint32_from_be
Reads a 32-bit big-endian coded integer from 4 bytes of data.
Parameters:
bytes - Pointer to the bytes to interpret.
Returns:
The read integer.
*/
inline unsigned uint32_from_be(const void *bytes)
{
const unsigned char *b = (const unsigned char *)bytes;
return (b[0]<<24)|(b[1]<<16)|(b[2]<<8)|b[3];
}
/*
Function: uint32_to_be
Writes a 32-bit integer into 4 bytes of data, coded as
big-endian.
Parameters:
bytes - The place to write the integer to.
integer - The integer to write.
*/
inline void uint32_to_be(void *bytes, unsigned integer)
{
unsigned char *b = (unsigned char *)bytes;
b[0] = (integer&0xff000000)>>24;
b[1] = (integer&0x00ff0000)>>16;
b[2] = (integer&0x0000ff00)>>8;
b[3] = (integer&0x000000ff)>>0;
}
/*
Function: secure_random_init
Initializes the secure random module.
@@ -25,6 +25,7 @@ MACRO_CONFIG_INT(ClAutoScreenshot, cl_auto_screenshot, 0, 0, 1, CFGFLAG_SAVE|CFG
MACRO_CONFIG_INT(ClAutoScreenshotMax, cl_auto_screenshot_max, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Maximum number of automatically created screenshots (0 = no limit)")
MACRO_CONFIG_INT(ClEventthread, cl_eventthread, 0, 0, 1, CFGFLAG_CLIENT, "Enables the usage of a thread to pump the events")
MACRO_CONFIG_INT(ClAllowOldServers, cl_allow_old_servers, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Allow connecting to servers that do not furtherly secure the connection")
MACRO_CONFIG_INT(InpGrab, inp_grab, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use forceful input grabbing method")
@@ -82,6 +83,7 @@ MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server
MACRO_CONFIG_STR(Bindaddr, bindaddr, 128, "", CFGFLAG_CLIENT|CFGFLAG_SERVER|CFGFLAG_MASTER, "Address to bind the client/server to")
MACRO_CONFIG_INT(SvPort, sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server")
MACRO_CONFIG_INT(SvExternalPort, sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers")
MACRO_CONFIG_INT(SvAllowOldClients, sv_allow_old_clients, 1, 0, 1, CFGFLAG_SERVER, "Allow clients to connect that do not support the anti-spoof protocol")
MACRO_CONFIG_STR(SvMap, sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server")
MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server")
MACRO_CONFIG_INT(SvMaxClientsPerIP, sv_max_clients_per_ip, 4, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients with the same IP that can connect to the server")
@@ -117,8 +117,18 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct
io_flush(ms_DataLogSent);
}
// compress
CompressedSize = ms_Huffman.Compress(pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4);
int HeaderSize = NET_PACKETHEADERSIZE_WITHOUT_TOKEN;
if(pPacket->m_Flags&NET_PACKETFLAG_TOKEN)
{
HeaderSize = NET_PACKETHEADERSIZE;
uint32_to_be(&aBuffer[3], pPacket->m_Token);
}
if(!(pPacket->m_Flags&NET_PACKETFLAG_CONTROL))
{
// compress
CompressedSize = ms_Huffman.Compress(pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[HeaderSize], NET_MAX_PACKETSIZE-HeaderSize);
}
// check if the compression was enabled, successful and good enough
if(CompressedSize > 0 && CompressedSize < pPacket->m_DataSize)
@@ -130,15 +140,15 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct
{
// use uncompressed data
FinalSize = pPacket->m_DataSize;
mem_copy(&aBuffer[3], pPacket->m_aChunkData, pPacket->m_DataSize);
mem_copy(&aBuffer[HeaderSize], pPacket->m_aChunkData, pPacket->m_DataSize);
pPacket->m_Flags &= ~NET_PACKETFLAG_COMPRESSION;
}
// set header and send the packet if all things are good
if(FinalSize >= 0)
{
FinalSize += NET_PACKETHEADERSIZE;
aBuffer[0] = ((pPacket->m_Flags<<4)&0xf0)|((pPacket->m_Ack>>8)&0xf);
FinalSize += HeaderSize;
aBuffer[0] = ((pPacket->m_Flags<<2)&0xfc)|((pPacket->m_Ack>>8)&0x3);
aBuffer[1] = pPacket->m_Ack&0xff;
aBuffer[2] = pPacket->m_NumChunks;
net_udp_send(Socket, pAddr, aBuffer, FinalSize);
@@ -159,9 +169,12 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct
int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket)
{
// check the size
if(Size < NET_PACKETHEADERSIZE || Size > NET_MAX_PACKETSIZE)
if(Size < NET_PACKETHEADERSIZE_WITHOUT_TOKEN || Size > NET_MAX_PACKETSIZE)
{
dbg_msg("", "packet too small, %d", Size);
if(g_Config.m_Debug)
{
dbg_msg("net", "packet too small, %d", Size);
}
return -1;
}
@@ -176,16 +189,20 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct
}
// read the packet
pPacket->m_Flags = pBuffer[0]>>4;
pPacket->m_Ack = ((pBuffer[0]&0xf)<<8) | pBuffer[1];
pPacket->m_Flags = pBuffer[0]>>2;
pPacket->m_Ack = ((pBuffer[0]&0x3)<<8) | pBuffer[1];
pPacket->m_NumChunks = pBuffer[2];
pPacket->m_DataSize = Size - NET_PACKETHEADERSIZE;
pPacket->m_DataSize = Size - NET_PACKETHEADERSIZE_WITHOUT_TOKEN;
pPacket->m_Token = 0;
if(pPacket->m_Flags&NET_PACKETFLAG_CONNLESS)
{
if(Size < 6)
{
dbg_msg("", "connection less packet too small, %d", Size);
if(g_Config.m_Debug)
{
dbg_msg("net", "connection less packet too small, %d", Size);
}
return -1;
}
@@ -197,10 +214,25 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct
}
else
{
unsigned char *pDataStart = &pBuffer[NET_PACKETHEADERSIZE_WITHOUT_TOKEN];
if(pPacket->m_Flags&NET_PACKETFLAG_TOKEN)
{
if(Size < NET_PACKETHEADERSIZE)
{
if(g_Config.m_Debug)
{
dbg_msg("net", "packet with token too small, %d", Size);
}
return -1;
}
pPacket->m_DataSize -= 4;
pDataStart = &pBuffer[NET_PACKETHEADERSIZE];
pPacket->m_Token = uint32_from_be(&pBuffer[3]);
}
if(pPacket->m_Flags&NET_PACKETFLAG_COMPRESSION)
pPacket->m_DataSize = ms_Huffman.Decompress(&pBuffer[3], pPacket->m_DataSize, pPacket->m_aChunkData, sizeof(pPacket->m_aChunkData));
pPacket->m_DataSize = ms_Huffman.Decompress(pDataStart, pPacket->m_DataSize, pPacket->m_aChunkData, sizeof(pPacket->m_aChunkData));
else
mem_copy(pPacket->m_aChunkData, &pBuffer[3], pPacket->m_DataSize);
mem_copy(pPacket->m_aChunkData, pDataStart, pPacket->m_DataSize);
}
// check for errors
@@ -226,12 +258,13 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct
}
void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize)
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;
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);
@@ -9,14 +9,15 @@
/*
CURRENT:
packet header: 3 bytes
unsigned char flags_ack; // 4bit flags, 4bit ack
packet header: 7 bytes
unsigned char flags_ack; // 6bit flags, 2bit ack
unsigned char ack; // 8 bit ack
unsigned char num_chunks; // 8 bit chunks
(unsigned char token[4];) // 32 bit token if flags contains NET_PACKETFLAG_TOKEN
(unsigned char padding[3]) // 24 bit extra incase it's a connection less packet
// this is to make sure that it's compatible with the
// old protocol
(unsigned char padding[3];) // 24 bit extra in case it's a connection less packet
// this is to make sure that it's compatible with the
// old protocol
chunk header: 2-3 bytes
unsigned char flags_size; // 2bit flags, 6 bit size
@@ -47,32 +48,33 @@ enum
NET_VERSION = 2,
NET_MAX_PACKETSIZE = 1400,
NET_MAX_PAYLOAD = NET_MAX_PACKETSIZE-6,
NET_MAX_PAYLOAD = NET_MAX_PACKETSIZE-10,
NET_MAX_CHUNKHEADERSIZE = 5,
NET_PACKETHEADERSIZE = 3,
NET_PACKETHEADERSIZE = 7,
NET_PACKETHEADERSIZE_WITHOUT_TOKEN = 3,
NET_MAX_CLIENTS = 16,
NET_MAX_CONSOLE_CLIENTS = 4,
NET_MAX_SEQUENCE = 1<<10,
NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1,
NET_CONNSTATE_OFFLINE=0,
NET_CONNSTATE_CONNECT=1,
NET_CONNSTATE_PENDING=2,
NET_CONNSTATE_ONLINE=3,
NET_CONNSTATE_ERROR=4,
NET_PACKETFLAG_CONTROL=1,
NET_PACKETFLAG_CONNLESS=2,
NET_PACKETFLAG_RESEND=4,
NET_PACKETFLAG_COMPRESSION=8,
NET_PACKETFLAG_UNUSED=1<<0,
NET_PACKETFLAG_TOKEN=1<<1,
NET_PACKETFLAG_CONTROL=1<<2,
NET_PACKETFLAG_CONNLESS=1<<3,
NET_PACKETFLAG_RESEND=1<<4,
NET_PACKETFLAG_COMPRESSION=1<<5,
NET_CHUNKFLAG_VITAL=1,
NET_CHUNKFLAG_RESEND=2,
NET_CTRLMSG_KEEPALIVE=0,
NET_CTRLMSG_CONNECT=1,
NET_CTRLMSG_CONNECTACCEPT=2,
NET_CTRLMSG_ACCEPT=3,
NET_CTRLMSG_CLOSE=4,
NET_CONN_BUFFERSIZE=1024*32,
@@ -125,6 +127,7 @@ class CNetPacketConstruct
int m_Ack;
int m_NumChunks;
int m_DataSize;
unsigned m_Token;
unsigned char m_aChunkData[NET_MAX_PAYLOAD];
};
@@ -141,7 +144,8 @@ class CNetConnection
unsigned short m_PeerAck;
unsigned m_State;
int m_Token;
bool m_UseToken;
unsigned m_Token;
int m_RemoteClosed;
bool m_BlockCloseMsg;
@@ -169,10 +173,12 @@ class CNetConnection
void SendControl(int ControlMsg, const void *pExtra, int ExtraSize);
void ResendChunk(CNetChunkResend *pResend);
void Resend();
void SendConnect();
public:
void Init(NETSOCKET Socket, bool BlockCloseMsg);
int Connect(NETADDR *pAddr);
int Accept(NETADDR *pAddr, unsigned Token);
void Disconnect(const char *pReason);
int Update();
@@ -263,8 +269,16 @@ class CNetServer
NETFUNC_DELCLIENT m_pfnDelClient;
void *m_UserPtr;
int m_CurrentSalt;
unsigned char m_aaSalts[2][16];
int64 m_LastSaltUpdate;
CNetRecvUnpacker m_RecvUnpacker;
unsigned GetToken(const NETADDR &Addr) const;
unsigned GetToken(const NETADDR &Addr, int SaltIndex) const;
bool IsCorrectToken(const NETADDR &Addr, unsigned Token) const;
public:
int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
@@ -379,7 +393,7 @@ class CNetBase
static int Compress(const void *pData, int DataSize, void *pOutput, int OutputSize);
static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize);
static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize);
static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, bool UseToken, unsigned Token, int ControlMsg, const void *pExtra, int ExtraSize);
static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize);
static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket);
static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket);
Oops, something went wrong.

0 comments on commit a263185

Please sign in to comment.