ddnet/src/engine/shared/network.h
heinrich5991 6b65ccb945 Add HTTP masterserver registering and HTTP masterserver
Registering
-----------

The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.

The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.

The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0

{
	"max_clients": 64,
	"max_players": 64,
	"passworded": false,
	"game_type": "TestDDraceNetwork",
	"name": "My DDNet server",
	"map": {
		"name": "dm1",
		"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
		"size": 5805
	},
	"version": "0.6.4, 16.0.3",
	"clients": []
}
```

The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.

The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.

The free-form `Challenge-Secret` is sent back via UDP for a port forward
check.  This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.

The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.

The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.

Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.

Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.

The masterserver responds with a `200 OK` with a body like this:

```
{"status":"success"}
```

The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.

It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.

Synchronization
---------------

The masterserver keeps state and outputs JSON files every second.

```json
{
	"servers": [
		{
			"addresses": [
				"tw-0.6+udp://127.0.0.1:8303",
				"tw-0.6+udp://[::1]:8303"
			],
			"info_serial": 0,
			"info": {
				"max_clients": 64,
				"max_players": 64,
				"passworded": false,
				"game_type": "TestDDraceNetwork",
				"name": "My DDNet server",
				"map": {
					"name": "dm1",
					"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
					"size": 5805
				},
				"version": "0.6.4, 16.0.3",
				"clients": []
			}
		}
	]
}
```

`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.

```json
{
	"kind": "mastersrv",
	"now": 1816002,
	"secrets": {
		"tw-0.6+udp://127.0.0.1:8303": {
			"ping_time": 1811999,
			"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
		},
		"tw-0.6+udp://[::1]:8303": {
			"ping_time": 1811999,
			"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
		}
	},
	"servers": {
		"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
			"info_serial": 0,
			"info": {
				"max_clients": 64,
				"max_players": 64,
				"passworded": false,
				"game_type": "TestDDraceNetwork",
				"name": "My DDNet server",
				"map": {
					"name": "dm1",
					"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
					"size": 5805
				},
				"version": "0.6.4, 16.0.3",
				"clients": []
			}
		}
	}
}
```

`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.

The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.

The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.

`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.

`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.

```json
[
	"tw-0.6+udp://127.0.0.1:8303",
	"tw-0.6+udp://[::1]:8303"
]
```

`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.

This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.

An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.

All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.

Implementation
--------------

The masterserver implementation was done in Rust.

The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-20 08:58:32 +02:00

539 lines
14 KiB
C++

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef ENGINE_SHARED_NETWORK_H
#define ENGINE_SHARED_NETWORK_H
#include "huffman.h"
#include "ringbuffer.h"
#include "stun.h"
#include <base/math.h>
#include <engine/message.h>
/*
CURRENT:
packet header: 3 bytes
unsigned char flags_ack; // 6bit flags, 2bit ack
0.6: ORNCaaAA
0.6.5: ORNCTUAA
0.7: --NORCAA
unsigned char ack; // 8 bit ack
unsigned char num_chunks; // 8 bit chunks
(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
unsigned char size_seq; // 4bit size, 4bit seq
(unsigned char seq;) // 8bit seq, if vital flag is set
*/
enum
{
NETFLAG_ALLOWSTATELESS = 1,
NETSENDFLAG_VITAL = 1,
NETSENDFLAG_CONNLESS = 2,
NETSENDFLAG_FLUSH = 4,
NETSENDFLAG_EXTENDED = 8,
NETSTATE_OFFLINE = 0,
NETSTATE_CONNECTING,
NETSTATE_ONLINE,
NETBANTYPE_SOFT = 1,
NETBANTYPE_DROP = 2
};
enum
{
NET_VERSION = 2,
NET_MAX_PACKETSIZE = 1400,
NET_MAX_PAYLOAD = NET_MAX_PACKETSIZE - 6,
NET_MAX_CHUNKHEADERSIZE = 5,
NET_PACKETHEADERSIZE = 3,
NET_MAX_CLIENTS = 64,
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_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,
// NOT SENT VIA THE NETWORK DIRECTLY:
NET_PACKETFLAG_EXTENDED = 1 << 6,
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,
NET_CONNLIMIT_IPS = 16,
NET_ENUM_TERMINATOR
};
typedef int SECURITY_TOKEN;
SECURITY_TOKEN ToSecurityToken(unsigned char *pData);
extern const unsigned char SECURITY_TOKEN_MAGIC[4];
enum
{
NET_SECURITY_TOKEN_UNKNOWN = -1,
NET_SECURITY_TOKEN_UNSUPPORTED = 0,
};
typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char *pReason, void *pUser);
typedef int (*NETFUNC_NEWCLIENT_CON)(int ClientID, void *pUser);
typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser, bool Sixup);
typedef int (*NETFUNC_NEWCLIENT_NOAUTH)(int ClientID, void *pUser);
typedef int (*NETFUNC_CLIENTREJOIN)(int ClientID, void *pUser);
struct CNetChunk
{
// -1 means that it's a stateless packet
// 0 on the client means the server
int m_ClientID;
NETADDR m_Address; // only used when client_id == -1
int m_Flags;
int m_DataSize;
const void *m_pData;
// only used if the flags contain NETSENDFLAG_EXTENDED and NETSENDFLAG_CONNLESS
unsigned char m_aExtraData[4];
};
class CNetChunkHeader
{
public:
int m_Flags;
int m_Size;
int m_Sequence;
unsigned char *Pack(unsigned char *pData, int Split = 4);
unsigned char *Unpack(unsigned char *pData, int Split = 4);
};
class CNetChunkResend
{
public:
int m_Flags;
int m_DataSize;
unsigned char *m_pData;
int m_Sequence;
int64_t m_LastSendTime;
int64_t m_FirstSendTime;
};
class CNetPacketConstruct
{
public:
int m_Flags;
int m_Ack;
int m_NumChunks;
int m_DataSize;
unsigned char m_aChunkData[NET_MAX_PAYLOAD];
unsigned char m_aExtraData[4];
};
enum class CONNECTIVITY
{
UNKNOWN,
CHECKING,
UNREACHABLE,
REACHABLE,
ADDRESS_KNOWN,
};
class CStun
{
class CProtocol
{
int m_Index;
NETSOCKET m_Socket;
CStunData m_Stun;
bool m_HaveStunServer = false;
NETADDR m_StunServer;
bool m_HaveAddr = false;
NETADDR m_Addr;
int64_t m_LastResponse = -1;
int64_t m_NextTry = -1;
int m_NumUnsuccessfulTries = -1;
public:
CProtocol(int Index, NETSOCKET Socket);
void FeedStunServer(NETADDR StunServer);
void Refresh();
void Update();
bool OnPacket(NETADDR Addr, unsigned char *pData, int DataSize);
CONNECTIVITY GetConnectivity(NETADDR *pGlobalAddr);
};
CProtocol m_aProtocols[2];
public:
CStun(NETSOCKET Socket);
void FeedStunServer(NETADDR StunServer);
void Refresh();
void Update();
bool OnPacket(NETADDR Addr, unsigned char *pData, int DataSize);
CONNECTIVITY GetConnectivity(int NetType, NETADDR *pGlobalAddr);
};
class CNetConnection
{
// TODO: is this needed because this needs to be aware of
// the ack sequencing number and is also responible for updating
// that. this should be fixed.
friend class CNetRecvUnpacker;
private:
unsigned short m_Sequence;
unsigned short m_Ack;
unsigned short m_PeerAck;
unsigned m_State;
SECURITY_TOKEN m_SecurityToken;
int m_RemoteClosed;
bool m_BlockCloseMsg;
bool m_UnknownSeq;
CStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> m_Buffer;
int64_t m_LastUpdateTime;
int64_t m_LastRecvTime;
int64_t m_LastSendTime;
char m_aErrorString[256];
CNetPacketConstruct m_Construct;
NETADDR m_PeerAddr;
NETSOCKET m_Socket;
NETSTATS m_Stats;
//
void ResetStats();
void SetError(const char *pString);
void AckChunks(int Ack);
int QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence);
void SendControl(int ControlMsg, const void *pExtra, int ExtraSize);
void ResendChunk(CNetChunkResend *pResend);
void Resend();
public:
bool m_TimeoutProtected;
bool m_TimeoutSituation;
void Reset(bool Rejoin = false);
void Init(NETSOCKET Socket, bool BlockCloseMsg);
int Connect(NETADDR *pAddr);
void Disconnect(const char *pReason);
int Update();
int Flush();
int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED);
int QueueChunk(int Flags, int DataSize, const void *pData);
const char *ErrorString();
void SignalResend();
int State() const { return m_State; }
const NETADDR *PeerAddress() const { return &m_PeerAddr; }
void ResetErrorString() { m_aErrorString[0] = 0; }
const char *ErrorString() const { return m_aErrorString; }
// Needed for GotProblems in NetClient
int64_t LastRecvTime() const { return m_LastRecvTime; }
int64_t ConnectTime() const { return m_LastUpdateTime; }
int AckSequence() const { return m_Ack; }
int SeqSequence() const { return m_Sequence; }
int SecurityToken() const { return m_SecurityToken; }
CStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> *ResendBuffer() { return &m_Buffer; }
void SetTimedOut(const NETADDR *pAddr, int Sequence, int Ack, SECURITY_TOKEN SecurityToken, CStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> *pResendBuffer, bool Sixup);
// anti spoof
void DirectInit(NETADDR &Addr, SECURITY_TOKEN SecurityToken, SECURITY_TOKEN Token, bool Sixup);
void SetUnknownSeq() { m_UnknownSeq = true; }
void SetSequence(int Sequence) { m_Sequence = Sequence; }
bool m_Sixup;
SECURITY_TOKEN m_Token;
};
class CConsoleNetConnection
{
private:
int m_State;
NETADDR m_PeerAddr;
NETSOCKET m_Socket;
char m_aBuffer[NET_MAX_PACKETSIZE];
int m_BufferOffset;
char m_aErrorString[256];
bool m_LineEndingDetected;
char m_aLineEnding[3];
public:
void Init(NETSOCKET Socket, const NETADDR *pAddr);
void Disconnect(const char *pReason);
int State() const { return m_State; }
const NETADDR *PeerAddress() const { return &m_PeerAddr; }
const char *ErrorString() const { return m_aErrorString; }
void Reset();
int Update();
int Send(const char *pLine);
int Recv(char *pLine, int MaxLength);
};
class CNetRecvUnpacker
{
public:
bool m_Valid;
NETADDR m_Addr;
CNetConnection *m_pConnection;
int m_CurrentChunk;
int m_ClientID;
CNetPacketConstruct m_Data;
unsigned char m_aBuffer[NET_MAX_PACKETSIZE];
CNetRecvUnpacker() { Clear(); }
void Clear();
void Start(const NETADDR *pAddr, CNetConnection *pConnection, int ClientID);
int FetchChunk(CNetChunk *pChunk);
};
// server side
class CNetServer
{
struct CSlot
{
public:
CNetConnection m_Connection;
};
struct CSpamConn
{
NETADDR m_Addr;
int64_t m_Time;
int m_Conns;
};
NETADDR m_Address;
NETSOCKET m_Socket;
class CNetBan *m_pNetBan;
CSlot m_aSlots[NET_MAX_CLIENTS];
int m_MaxClients;
int m_MaxClientsPerIP;
NETFUNC_NEWCLIENT m_pfnNewClient;
NETFUNC_NEWCLIENT_NOAUTH m_pfnNewClientNoAuth;
NETFUNC_DELCLIENT m_pfnDelClient;
NETFUNC_CLIENTREJOIN m_pfnClientRejoin;
void *m_pUser;
int m_NumConAttempts; // log flooding attacks
int64_t m_TimeNumConAttempts;
unsigned char m_aSecurityTokenSeed[16];
// vanilla connect flood detection
int64_t m_VConnFirst;
int m_VConnNum;
CSpamConn m_aSpamConns[NET_CONNLIMIT_IPS];
CNetRecvUnpacker m_RecvUnpacker;
void OnTokenCtrlMsg(NETADDR &Addr, int ControlMsg, const CNetPacketConstruct &Packet);
int OnSixupCtrlMsg(NETADDR &Addr, CNetChunk *pChunk, int ControlMsg, const CNetPacketConstruct &Packet, SECURITY_TOKEN &ResponseToken, SECURITY_TOKEN Token);
void OnPreConnMsg(NETADDR &Addr, CNetPacketConstruct &Packet);
void OnConnCtrlMsg(NETADDR &Addr, int ClientID, int ControlMsg, const CNetPacketConstruct &Packet);
bool ClientExists(const NETADDR &Addr) { return GetClientSlot(Addr) != -1; }
int GetClientSlot(const NETADDR &Addr);
void SendControl(NETADDR &Addr, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken);
int TryAcceptClient(NETADDR &Addr, SECURITY_TOKEN SecurityToken, bool VanillaAuth = false, bool Sixup = false, SECURITY_TOKEN Token = 0);
int NumClientsWithAddr(NETADDR Addr);
bool Connlimit(NETADDR Addr);
void SendMsgs(NETADDR &Addr, const CMsgPacker *apMsgs[], int Num);
public:
int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_NEWCLIENT_NOAUTH pfnNewClientNoAuth, NETFUNC_CLIENTREJOIN pfnClientRejoin, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
//
bool Open(NETADDR BindAddr, class CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP);
int Close();
//
int Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken);
int Send(CNetChunk *pChunk);
int Update();
//
int Drop(int ClientID, const char *pReason);
// status requests
const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
bool HasSecurityToken(int ClientID) const { return m_aSlots[ClientID].m_Connection.SecurityToken() != NET_SECURITY_TOKEN_UNSUPPORTED; }
NETADDR Address() const { return m_Address; }
NETSOCKET Socket() const { return m_Socket; }
class CNetBan *NetBan() const { return m_pNetBan; }
int NetType() const { return net_socket_type(m_Socket); }
int MaxClients() const { return m_MaxClients; }
void SendTokenSixup(NETADDR &Addr, SECURITY_TOKEN Token);
int SendConnlessSixup(CNetChunk *pChunk, SECURITY_TOKEN ResponseToken);
//
void SetMaxClientsPerIP(int Max);
bool SetTimedOut(int ClientID, int OrigID);
void SetTimeoutProtected(int ClientID);
int ResetErrorString(int ClientID);
const char *ErrorString(int ClientID);
// anti spoof
SECURITY_TOKEN GetGlobalToken();
SECURITY_TOKEN GetToken(const NETADDR &Addr);
// vanilla token/gametick shouldn't be negative
SECURITY_TOKEN GetVanillaToken(const NETADDR &Addr) { return absolute(GetToken(Addr)); }
};
class CNetConsole
{
struct CSlot
{
CConsoleNetConnection m_Connection;
};
NETSOCKET m_Socket;
class CNetBan *m_pNetBan;
CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS];
NETFUNC_NEWCLIENT_CON m_pfnNewClient;
NETFUNC_DELCLIENT m_pfnDelClient;
void *m_pUser;
CNetRecvUnpacker m_RecvUnpacker;
public:
void SetCallbacks(NETFUNC_NEWCLIENT_CON pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
//
bool Open(NETADDR BindAddr, class CNetBan *pNetBan);
int Close();
//
int Recv(char *pLine, int MaxLength, int *pClientID = 0);
int Send(int ClientID, const char *pLine);
int Update();
//
int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
int Drop(int ClientID, const char *pReason);
// status requests
const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
class CNetBan *NetBan() const { return m_pNetBan; }
};
// client side
class CNetClient
{
CNetConnection m_Connection;
CNetRecvUnpacker m_RecvUnpacker;
CStun *m_pStun = nullptr;
public:
NETSOCKET m_Socket;
// openness
bool Open(NETADDR BindAddr);
int Close();
// connection state
int Disconnect(const char *pReason);
int Connect(NETADDR *pAddr);
// communication
int Recv(CNetChunk *pChunk);
int Send(CNetChunk *pChunk);
// pumping
int Update();
int Flush();
int ResetErrorString();
// error and state
int NetType() const { return net_socket_type(m_Socket); }
int State();
int GotProblems(int64_t MaxLatency) const;
const char *ErrorString() const;
bool SecurityTokenUnknown() { return m_Connection.SecurityToken() == NET_SECURITY_TOKEN_UNKNOWN; }
// stun
void FeedStunServer(NETADDR StunServer);
void RefreshStun();
CONNECTIVITY GetConnectivity(int NetType, NETADDR *pGlobalAddr);
};
// TODO: both, fix these. This feels like a junk class for stuff that doesn't fit anywere
class CNetBase
{
static IOHANDLE ms_DataLogSent;
static IOHANDLE ms_DataLogRecv;
static CHuffman ms_Huffman;
public:
static void OpenLog(IOHANDLE DataLogSent, IOHANDLE DataLogRecv);
static void CloseLog();
static void Init();
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, SECURITY_TOKEN SecurityToken, bool Sixup = false);
static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize, bool Extended, unsigned char aExtra[4]);
static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket, SECURITY_TOKEN SecurityToken, bool Sixup = false, bool NoCompress = false);
static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket, bool &Sixup, SECURITY_TOKEN *pSecurityToken = 0, SECURITY_TOKEN *pResponseToken = 0);
// The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not
static int IsSeqInBackroom(int Seq, int Ack);
};
#endif