summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorheinrich5991 <heinrich5991@gmail.com>2018-09-27 13:22:16 +0200
committerheinrich5991 <heinrich5991@gmail.com>2018-10-12 22:09:04 +0200
commita263185571903ead01f6b351a91ea219ac9d215f (patch)
tree5a88d388d00456ae5fd2d946f14eed249f2ec86e
parent480de5cf65b28ee8ef30af987bf9f363aa39f778 (diff)
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).
-rw-r--r--src/base/system.h34
-rw-r--r--src/engine/shared/config_variables.h2
-rw-r--r--src/engine/shared/network.cpp63
-rw-r--r--src/engine/shared/network.h44
-rw-r--r--src/engine/shared/network_conn.cpp166
-rw-r--r--src/engine/shared/network_server.cpp143
6 files changed, 336 insertions, 116 deletions
diff --git a/src/base/system.h b/src/base/system.h
index c4fb1a4c5..fef36f0b6 100644
--- a/src/base/system.h
+++ b/src/base/system.h
@@ -1295,6 +1295,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.
You *MUST* check the return value of this function.
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index 025a9cf38..e62ddf37b 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -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")
diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp
index 13d62077d..6a2eb921c 100644
--- a/src/engine/shared/network.cpp
+++ b/src/engine/shared/network.cpp
@@ -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);
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index b934563cb..90cc1076b 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -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,9 +48,10 @@ 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,
@@ -57,14 +59,15 @@ enum
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,
@@ -72,7 +75,6 @@ enum
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 @@ public:
int m_Ack;
int m_NumChunks;
int m_DataSize;
+ unsigned m_Token;
unsigned char m_aChunkData[NET_MAX_PAYLOAD];
};
@@ -141,7 +144,8 @@ private:
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 @@ private:
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 @@ public:
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);
diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp
index ff885093b..0af9d48d5 100644
--- a/src/engine/shared/network_conn.cpp
+++ b/src/engine/shared/network_conn.cpp
@@ -20,7 +20,8 @@ void CNetConnection::Reset()
m_LastSendTime = 0;
m_LastRecvTime = 0;
m_LastUpdateTime = 0;
- m_Token = -1;
+ m_UseToken = true;
+ m_Token = 0;
mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
m_Buffer.Init();
@@ -76,6 +77,11 @@ int CNetConnection::Flush()
// send of the packets
m_Construct.m_Ack = m_Ack;
+ if(m_UseToken)
+ {
+ m_Construct.m_Flags |= NET_PACKETFLAG_TOKEN;
+ m_Construct.m_Token = m_Token;
+ }
CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct);
// update send times
@@ -146,7 +152,8 @@ void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSi
{
// send the control message
m_LastSendTime = time_get();
- CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize);
+ bool UseToken = m_UseToken && ControlMsg != NET_CTRLMSG_CONNECT;
+ CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, UseToken, m_Token, ControlMsg, pExtra, ExtraSize);
}
void CNetConnection::ResendChunk(CNetChunkResend *pResend)
@@ -161,6 +168,13 @@ void CNetConnection::Resend()
ResendChunk(pResend);
}
+void CNetConnection::SendConnect()
+{
+ unsigned char aConnect[512] = {0};
+ uint32_to_be(&aConnect[4], m_Token);
+ SendControl(NET_CTRLMSG_CONNECT, aConnect, sizeof(aConnect));
+}
+
int CNetConnection::Connect(NETADDR *pAddr)
{
if(State() != NET_CONNSTATE_OFFLINE)
@@ -171,7 +185,26 @@ int CNetConnection::Connect(NETADDR *pAddr)
m_PeerAddr = *pAddr;
mem_zero(m_ErrorString, sizeof(m_ErrorString));
m_State = NET_CONNSTATE_CONNECT;
- SendControl(NET_CTRLMSG_CONNECT, 0, 0);
+ secure_random_fill(&m_Token, sizeof(m_Token));
+ SendConnect();
+ return 0;
+}
+
+int CNetConnection::Accept(NETADDR *pAddr, unsigned Token)
+{
+ if(State() != NET_CONNSTATE_OFFLINE)
+ return -1;
+
+ // init connection
+ Reset();
+ m_PeerAddr = *pAddr;
+ mem_zero(m_ErrorString, sizeof(m_ErrorString));
+ m_State = NET_CONNSTATE_ONLINE;
+ m_Token = Token;
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("connection", "connecting online");
+ }
return 0;
}
@@ -201,6 +234,53 @@ void CNetConnection::Disconnect(const char *pReason)
int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
{
+ int64 Now = time_get();
+
+ if(m_UseToken)
+ {
+ if(!(pPacket->m_Flags&NET_PACKETFLAG_TOKEN))
+ {
+ if(!(pPacket->m_Flags&NET_PACKETFLAG_CONTROL) || pPacket->m_DataSize < 1)
+ {
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("connection", "dropping msg without token");
+ }
+ return 0;
+ }
+ if(pPacket->m_aChunkData[0] == NET_CTRLMSG_CONNECTACCEPT)
+ {
+ if(!g_Config.m_ClAllowOldServers)
+ {
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("connection", "dropping connect+accept without token");
+ }
+ return 0;
+ }
+ }
+ else
+ {
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("connection", "dropping ctrl msg without token");
+ }
+ return 0;
+ }
+ }
+ else
+ {
+ if(pPacket->m_Token != m_Token)
+ {
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("connection", "dropping msg with invalid token, wanted=%08x got=%08x", m_Token, pPacket->m_Token);
+ }
+ return 0;
+ }
+ }
+ }
+
// check if actual ack value is valid(own sequence..latest peer ack)
if(m_Sequence >= m_PeerAck)
{
@@ -214,8 +294,6 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
}
m_PeerAck = pPacket->m_Ack;
- int64 Now = time_get();
-
// check if resend is requested
if(pPacket->m_Flags&NET_PACKETFLAG_RESEND)
Resend();
@@ -232,54 +310,52 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
m_State = NET_CONNSTATE_ERROR;
m_RemoteClosed = 1;
- char Str[128] = {0};
+ 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(Str, (char *)&pPacket->m_aChunkData[1], pPacket->m_DataSize);
+ str_copy(aStr, (char *)&pPacket->m_aChunkData[1], pPacket->m_DataSize);
else
- str_copy(Str, (char *)&pPacket->m_aChunkData[1], sizeof(Str));
- str_sanitize_strong(Str);
+ str_copy(aStr, (char *)&pPacket->m_aChunkData[1], sizeof(aStr));
+ str_sanitize_strong(aStr);
}
if(!m_BlockCloseMsg)
{
// set the error string
- SetError(Str);
+ SetError(aStr);
}
if(g_Config.m_Debug)
- dbg_msg("conn", "closed reason='%s'", Str);
+ dbg_msg("connection", "closed reason='%s'", aStr);
}
return 0;
}
else
{
- if(State() == NET_CONNSTATE_OFFLINE)
- {
- if(CtrlMsg == NET_CTRLMSG_CONNECT)
- {
- // send response and init connection
- Reset();
- m_State = NET_CONNSTATE_PENDING;
- m_PeerAddr = *pAddr;
- mem_zero(m_ErrorString, sizeof(m_ErrorString));
- m_LastSendTime = Now;
- m_LastRecvTime = Now;
- m_LastUpdateTime = Now;
- SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0);
- if(g_Config.m_Debug)
- dbg_msg("connection", "got connection, sending connect+accept");
- }
- }
- else if(State() == NET_CONNSTATE_CONNECT)
+ 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;
- SendControl(NET_CTRLMSG_ACCEPT, 0, 0);
m_State = NET_CONNSTATE_ONLINE;
if(g_Config.m_Debug)
dbg_msg("connection", "got connect+accept, sending accept. connection online");
@@ -287,16 +363,6 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
}
}
}
- else
- {
- if(State() == NET_CONNSTATE_PENDING)
- {
- m_LastRecvTime = Now;
- m_State = NET_CONNSTATE_ONLINE;
- if(g_Config.m_Debug)
- dbg_msg("connection", "connecting online");
- }
- }
if(State() == NET_CONNSTATE_ONLINE)
{
@@ -310,6 +376,7 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
int CNetConnection::Update()
{
int64 Now = time_get();
+ int64 Freq = time_freq();
if(State() == NET_CONNSTATE_OFFLINE || State() == NET_CONNSTATE_ERROR)
return 0;
@@ -317,7 +384,7 @@ int CNetConnection::Update()
// check for timeout
if(State() != NET_CONNSTATE_OFFLINE &&
State() != NET_CONNSTATE_CONNECT &&
- (Now-m_LastRecvTime) > time_freq()*10)
+ (Now-m_LastRecvTime) > Freq*10)
{
m_State = NET_CONNSTATE_ERROR;
SetError("Timeout");
@@ -329,7 +396,7 @@ int CNetConnection::Update()
CNetChunkResend *pResend = m_Buffer.First();
// check if we have some really old stuff laying around and abort if not acked
- if(Now-pResend->m_FirstSendTime > time_freq()*10)
+ if(Now-pResend->m_FirstSendTime > Freq*10)
{
m_State = NET_CONNSTATE_ERROR;
SetError("Too weak connection (not acked for 10 seconds)");
@@ -337,7 +404,7 @@ int CNetConnection::Update()
else
{
// resend packet if we havn't got it acked in 1 second
- if(Now-pResend->m_LastSendTime > time_freq())
+ if(Now-pResend->m_LastSendTime > Freq)
ResendChunk(pResend);
}
}
@@ -345,25 +412,20 @@ int CNetConnection::Update()
// send keep alives if nothing has happend for 250ms
if(State() == NET_CONNSTATE_ONLINE)
{
- if(time_get()-m_LastSendTime > time_freq()/2) // flush connection after 500ms if needed
+ if(Now-m_LastSendTime > Freq/2) // flush connection after 500ms if needed
{
int NumFlushedChunks = Flush();
if(NumFlushedChunks && g_Config.m_Debug)
dbg_msg("connection", "flushed connection due to timeout. %d chunks.", NumFlushedChunks);
}
- if(time_get()-m_LastSendTime > time_freq())
+ if(Now-m_LastSendTime > Freq)
SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0);
}
else if(State() == NET_CONNSTATE_CONNECT)
{
- if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect every 500ms
- SendControl(NET_CTRLMSG_CONNECT, 0, 0);
- }
- else if(State() == NET_CONNSTATE_PENDING)
- {
- if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect/accept every 500ms
- SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0);
+ if(Now-m_LastSendTime > Freq/2) // send a new connect every 500ms
+ SendConnect();
}
return 0;
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
index 537048cbb..e12de47ad 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -3,6 +3,8 @@
#include <base/system.h>
#include <engine/console.h>
+#include <engine/external/md5/md5.h>
+#include <engine/shared/config.h>
#include "netban.h"
#include "network.h"
@@ -29,8 +31,11 @@ bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int Ma
m_MaxClientsPerIP = MaxClientsPerIP;
+ secure_random_fill(m_aaSalts, sizeof(m_aaSalts));
+ m_LastSaltUpdate = time_get();
+
for(int i = 0; i < NET_MAX_CLIENTS; i++)
- m_aSlots[i].m_Connection.Init(m_Socket, true);
+ m_aSlots[i].m_Connection.Init(m_Socket, !g_Config.m_Debug);
return true;
}
@@ -70,12 +75,19 @@ int CNetServer::Drop(int ClientID, const char *pReason)
int CNetServer::Update()
{
int64 Now = time_get();
+ int64 Freq = time_freq();
+ if(Now >= m_LastSaltUpdate + 10 * Freq)
+ {
+ m_CurrentSalt = (m_CurrentSalt + 1) % (sizeof(m_aaSalts) / sizeof(m_aaSalts[0]));
+ secure_random_fill(m_aaSalts[m_CurrentSalt], sizeof(m_aaSalts[m_CurrentSalt]));
+ m_LastSaltUpdate = Now;
+ }
for(int i = 0; i < MaxClients(); i++)
{
m_aSlots[i].m_Connection.Update();
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
{
- if(Now - m_aSlots[i].m_Connection.ConnectTime() < time_freq() && NetBan())
+ if(Now - m_aSlots[i].m_Connection.ConnectTime() < Freq && NetBan())
{
if(NetBan()->BanAddr(ClientAddr(i), 60, "Stressing network") == -1)
Drop(i, m_aSlots[i].m_Connection.ErrorString());
@@ -88,6 +100,34 @@ int CNetServer::Update()
return 0;
}
+unsigned CNetServer::GetToken(const NETADDR &Addr) const
+{
+ return GetToken(Addr, m_CurrentSalt);
+}
+
+unsigned CNetServer::GetToken(const NETADDR &Addr, int SaltIndex) const
+{
+ unsigned char aDigest[16];
+ md5_state_t Md5;
+ md5_init(&Md5);
+ md5_append(&Md5, (unsigned char *)&Addr, sizeof(Addr));
+ md5_append(&Md5, m_aaSalts[SaltIndex], sizeof(m_aaSalts[SaltIndex]));
+ md5_finish(&Md5, aDigest);
+ return uint32_from_be(aDigest);
+}
+
+bool CNetServer::IsCorrectToken(const NETADDR &Addr, unsigned Token) const
+{
+ for(unsigned i = 0; i < sizeof(m_aaSalts) / sizeof(m_aaSalts[0]); i++)
+ {
+ if(GetToken(Addr, i) == Token)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
/*
TODO: chopp up this function into smaller working parts
*/
@@ -110,12 +150,24 @@ int CNetServer::Recv(CNetChunk *pChunk)
if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
{
+ bool UseToken = false;
+ unsigned Token = 0;
+ if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_TOKEN)
+ {
+ UseToken = true;
+ Token = m_RecvUnpacker.m_Data.m_Token;
+ }
+ else if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT && m_RecvUnpacker.m_Data.m_DataSize >= 1+512)
+ {
+ UseToken = true;
+ Token = uint32_from_be(&m_RecvUnpacker.m_Data.m_aChunkData[5]);
+ }
// check if we just should drop the packet
char aBuf[128];
if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
{
// banned, reply with a message
- CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1);
+ CNetBase::SendControlMsg(m_Socket, &Addr, 0, UseToken, Token, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1);
continue;
}
@@ -130,25 +182,52 @@ int CNetServer::Recv(CNetChunk *pChunk)
}
else
{
- // TODO: check size here
- if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
+ int ClientID = -1;
+
+ // check if we already got this client
+ for(int i = 0; i < MaxClients(); i++)
{
- bool Found = false;
+ if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
+ net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
+ {
+ ClientID = i;
+ break;
+ }
+ }
- // check if we already got this client
- for(int i = 0; i < MaxClients(); i++)
+ 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)
{
- if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
- net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
+ if(g_Config.m_Debug)
{
- Found = true; // silent ignore.. we got this client already
- break;
+ dbg_msg("netserver", "dropping short connect packet, size=%d", m_RecvUnpacker.m_Data.m_DataSize);
}
+ continue;
}
+ 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));
+ }
+ else
+ {
// client that wants to connect
- if(!Found)
+ if(ClientID == -1)
{
+ if(!UseToken || !IsCorrectToken(Addr, Token))
+ {
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("netserver", "dropping packet with missing/invalid token, present=%d token=%08x", (int)UseToken, Token);
+ }
+ continue;
+ }
// only allow a specific number of players with the same ip
NETADDR ThisAddr = Addr, OtherAddr;
int FoundAddr = 1;
@@ -166,45 +245,41 @@ int CNetServer::Recv(CNetChunk *pChunk)
{
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP);
- CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf) + 1);
+ CNetBase::SendControlMsg(m_Socket, &Addr, 0, UseToken, Token, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf) + 1);
return 0;
}
}
}
+ int EmptySlot = -1;
for(int i = 0; i < MaxClients(); i++)
{
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
{
- Found = true;
- m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
- if(m_pfnNewClient)
- m_pfnNewClient(i, m_UserPtr);
+ EmptySlot = i;
break;
}
}
- if(!Found)
+ if(EmptySlot == -1)
{
- const char FullMsg[] = "This server is full";
- CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg));
+ const char aFullMsg[] = "This server is full";
+ CNetBase::SendControlMsg(m_Socket, &Addr, 0, UseToken, Token, NET_CTRLMSG_CLOSE, aFullMsg, sizeof(aFullMsg));
}
- }
- }
- else
- {
- // normal packet, find matching slot
- for(int i = 0; i < MaxClients(); i++)
- {
- if(net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
+ else
{
- if(m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
- {
- if(m_RecvUnpacker.m_Data.m_DataSize)
- m_RecvUnpacker.Start(&Addr, &m_aSlots[i].m_Connection, i);
- }
+ m_aSlots[EmptySlot].m_Connection.Accept(&Addr, Token);
+ m_aSlots[EmptySlot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
+ if(m_pfnNewClient)
+ m_pfnNewClient(EmptySlot, m_UserPtr);
}
}
+ else
+ {
+ // normal packet
+ if(m_RecvUnpacker.m_Data.m_DataSize)
+ m_RecvUnpacker.Start(&Addr, &m_aSlots[ClientID].m_Connection, ClientID);
+ }
}
}
}