5161 lines
140 KiB
C++
5161 lines
140 KiB
C++
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//=============================================================================//
|
|
// net_ws.c
|
|
// Windows IP Support layer.
|
|
|
|
#include "tier0/vprof.h"
|
|
#include "net_ws_headers.h"
|
|
#include "net_ws_queued_packet_sender.h"
|
|
#include "tier1/lzss.h"
|
|
#include "tier1/tokenset.h"
|
|
#include "matchmaking/imatchframework.h"
|
|
#include "tier2/tier2.h"
|
|
#include "ienginetoolinternal.h"
|
|
#include "server.h"
|
|
#include "mathlib/IceKey.H"
|
|
#include "steamdatagram/isteamdatagramclient.h"
|
|
#include "steamdatagram/isteamdatagramserver.h"
|
|
#include "steamdatagram/isteamnetworkingutils.h"
|
|
#include "engine/inetsupport.h"
|
|
|
|
#if !defined( _X360 ) && !defined( NO_STEAM )
|
|
#include "sv_steamauth.h"
|
|
#endif
|
|
|
|
#ifndef DEDICATED
|
|
#include "cl_steamauth.h"
|
|
#endif
|
|
|
|
#ifdef _PS3
|
|
#include <cell/sysmodule.h>
|
|
#endif
|
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define NET_COMPRESSION_STACKBUF_SIZE 4096
|
|
|
|
static ConVar net_showsplits( "net_showsplits", "0", FCVAR_RELEASE, "Show info about packet splits" );
|
|
|
|
static ConVar net_splitrate( "net_splitrate", "1", FCVAR_RELEASE, "Number of fragments for a splitpacket that can be sent per frame" );
|
|
|
|
static ConVar ipname ( "ip", "localhost", FCVAR_RELEASE, "Overrides IP for multihomed hosts" );
|
|
static ConVar ipname_tv ( "ip_tv", "", FCVAR_RELEASE, "Overrides IP used to bind TV port for multihomed hosts" );
|
|
static ConVar ipname_tv1 ( "ip_tv1", "", FCVAR_RELEASE, "Overrides IP used to bind TV1 port for multihomed hosts" );
|
|
static ConVar ipname_relay ( "ip_relay", "", FCVAR_RELEASE, "Overrides IP used to redirect TV relay connections for NAT hosts" );
|
|
static ConVar ipname_steam ( "ip_steam", "", FCVAR_RELEASE, "Overrides IP used to bind Steam port for multihomed hosts" );
|
|
static ConVar hostport ( "hostport", NETSTRING( PORT_SERVER ) , FCVAR_RELEASE, "Host game server port" );
|
|
static ConVar hostip ( "hostip", "", FCVAR_RELEASE, "Host game server ip" );
|
|
static ConVar net_public_adr( "net_public_adr", "", FCVAR_RELEASE, "For servers behind NAT/DHCP meant to be exposed to the public internet, this is the public facing ip address string: (\"x.x.x.x\" )" );
|
|
|
|
static ConVar clientport ( "clientport", NETSTRING( PORT_CLIENT ), FCVAR_RELEASE, "Host game client port" );
|
|
static ConVar hltvport ( "tv_port", NETSTRING( PORT_HLTV ), FCVAR_RELEASE, "Host GOTV[0] port" );
|
|
static ConVar hltvport1 ( "tv_port1", NETSTRING( PORT_HLTV1 ), FCVAR_RELEASE, "Host GOTV[1] port" );
|
|
#if defined( REPLAY_ENABLED )
|
|
static ConVar replayport ( "replay_port", va("%i",PORT_REPLAY), 0, "Host Replay port" );
|
|
#endif
|
|
|
|
static ConVar fakelag ( "net_fakelag", "0", FCVAR_CHEAT, "Lag all incoming network data (including loopback) by this many milliseconds." );
|
|
static ConVar fakeloss ( "net_fakeloss", "0", FCVAR_CHEAT, "Simulate packet loss as a percentage (negative means drop 1/n packets)" );
|
|
static ConVar droppackets ( "net_droppackets", "0", FCVAR_CHEAT, "Drops next n packets on client" );
|
|
static ConVar fakejitter ( "net_fakejitter", "0", FCVAR_CHEAT, "Jitter fakelag packet time" );
|
|
|
|
static ConVar net_compressvoice( "net_compressvoice", "0", 0, "Attempt to compress out of band voice payloads (360 only)." );
|
|
ConVar net_usesocketsforloopback( "net_usesocketsforloopback", "0",
|
|
#ifdef _DEBUG
|
|
FCVAR_RELEASE
|
|
#else
|
|
0
|
|
#endif
|
|
, "Use network sockets layer even for listen server local player's packets (multiplayer only)." );
|
|
|
|
static ConVar voice_verbose( "voice_verbose", "0", FCVAR_DEVELOPMENTONLY, "Turns on debug output with detailed spew about voice data processing." );
|
|
|
|
static ConVar voice_xsend_debug( "voice_xsend_debug", "0" );
|
|
|
|
#ifdef _DEBUG
|
|
static ConVar fakenoise ( "net_fakenoise", "0", FCVAR_CHEAT, "Simulate corrupt network packets (changes n bits per packet randomly)" );
|
|
static ConVar fakeshuffle ( "net_fakeshuffle", "0", FCVAR_CHEAT, "Shuffles order of every nth packet (needs net_fakelag)" );
|
|
static ConVar recvpackets ( "net_recvpackets", "-1", FCVAR_CHEAT, "Receive exactly next n packets if >= 0" );
|
|
static ConVar net_savelargesplits( "net_savelargesplits", "-1", 0, "If not -1, then if a split has this many or more split parts, save the entire packet to disc for analysis." );
|
|
#endif
|
|
|
|
#ifdef _X360
|
|
static void NET_LogServerCallback( IConVar *var, const char *pOldString, float flOldValue );
|
|
static ConVar net_logserver( "net_logserver", "0", 0, "Dump server stats to a file", NET_LogServerCallback );
|
|
static ConVar net_loginterval( "net_loginterval", "1", 0, "Time in seconds between server logs" );
|
|
#endif
|
|
|
|
static ConVar sv_steamdatagramtransport_port( "sv_steamdatagramtransport_port", "", FCVAR_RELEASE, "If non zero, listen for proxied traffic on the specified port" );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Toggle Xbox 360 network security to allow cross-platform testing
|
|
//-----------------------------------------------------------------------------
|
|
#if !defined( _X360 )
|
|
#define X360SecureNetwork() false
|
|
#define IPPROTO_VDP IPPROTO_UDP
|
|
#elif defined( _CERT )
|
|
#define X360SecureNetwork() true
|
|
#else
|
|
bool X360SecureNetwork( void )
|
|
{
|
|
if ( CommandLine()->FindParm( "-xnet_bypass_security" ) )
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
extern ConVar net_showudp;
|
|
extern ConVar net_showudp_oob;
|
|
extern ConVar net_showudp_remoteonly;
|
|
extern ConVar net_showtcp;
|
|
extern ConVar net_blocksize;
|
|
extern int host_framecount;
|
|
|
|
extern bool ShouldChecksumPackets();
|
|
extern unsigned short BufferToShortChecksum( const void *pvData, size_t nLength );
|
|
extern void NET_InitParanoidMode();
|
|
void NET_ClearQueuedPacketsForChannel( INetChannel *chan );
|
|
|
|
#define DEF_LOOPBACK_SIZE 2048
|
|
|
|
typedef struct
|
|
{
|
|
int nPort; // UDP/TCP use same port number
|
|
bool bListening; // true if TCP port is listening
|
|
int hUDP; // handle to UDP socket from socket()
|
|
int hTCP; // handle to TCP socket from socket()
|
|
} netsocket_t;
|
|
|
|
typedef struct
|
|
{
|
|
int newsock; // handle of new socket
|
|
int netsock; // handle of listen socket
|
|
float time;
|
|
netadr_t addr;
|
|
} pendingsocket_t;
|
|
|
|
|
|
#include "tier0/memdbgoff.h"
|
|
|
|
struct loopback_t
|
|
{
|
|
char *data; // loopback buffer
|
|
int datalen; // current data length
|
|
char defbuffer[ DEF_LOOPBACK_SIZE ];
|
|
|
|
DECLARE_FIXEDSIZE_ALLOCATOR( loopback_t );
|
|
};
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
DEFINE_FIXEDSIZE_ALLOCATOR( loopback_t, 2, CUtlMemoryPool::GROW_SLOW );
|
|
|
|
// Split long packets. Anything over 1460 is failing on some routers
|
|
typedef struct
|
|
{
|
|
int currentSequence;
|
|
int splitCount;
|
|
int totalSize;
|
|
int nExpectedSplitSize;
|
|
char buffer[ NET_MAX_MESSAGE ]; // This has to be big enough to hold the largest message
|
|
} LONGPACKET;
|
|
|
|
// Use this to pick apart the network stream, must be packed
|
|
#pragma pack(1)
|
|
typedef struct
|
|
{
|
|
int netID;
|
|
int sequenceNumber;
|
|
int packetID : 16;
|
|
int nSplitSize : 16;
|
|
} SPLITPACKET;
|
|
#pragma pack()
|
|
|
|
#define MIN_USER_MAXROUTABLE_SIZE 576 // ( X.25 Networks )
|
|
#define MAX_USER_MAXROUTABLE_SIZE MAX_ROUTABLE_PAYLOAD
|
|
|
|
|
|
#define MAX_SPLIT_SIZE (MAX_USER_MAXROUTABLE_SIZE - sizeof( SPLITPACKET ))
|
|
#define MIN_SPLIT_SIZE (MIN_USER_MAXROUTABLE_SIZE - sizeof( SPLITPACKET ))
|
|
|
|
static ConVar sv_maxroutable
|
|
(
|
|
"sv_maxroutable",
|
|
"1200",
|
|
0,
|
|
"Server upper bound on net_maxroutable that a client can use.",
|
|
true, MIN_USER_MAXROUTABLE_SIZE,
|
|
true, MAX_USER_MAXROUTABLE_SIZE
|
|
);
|
|
|
|
ConVar net_maxroutable
|
|
(
|
|
"net_maxroutable",
|
|
"1200",
|
|
FCVAR_ARCHIVE | FCVAR_USERINFO,
|
|
"Requested max packet size before packets are 'split'.",
|
|
true, MIN_USER_MAXROUTABLE_SIZE,
|
|
true, MAX_USER_MAXROUTABLE_SIZE
|
|
);
|
|
|
|
netadr_t net_local_adr;
|
|
double net_time = 0.0f; // current time, updated each frame
|
|
|
|
static CUtlVector<netsocket_t> net_sockets; // the sockets
|
|
static CUtlVector<netpacket_t> net_packets;
|
|
|
|
static bool net_multiplayer = false; // if true, configured for Multiplayer
|
|
static bool net_noip = false; // Disable IP support, can't switch to MP mode
|
|
static bool net_nodns = false; // Disable DNS request to avoid long timeouts
|
|
bool net_notcp = true; // Disable TCP support
|
|
static bool net_nohltv = false; // disable HLTV support
|
|
static bool net_addhltv1 = false; // by default, HLTV1 (sup)port is disabled. Must be enabled explicitly with -addhltv1 cmdline parameter
|
|
#if defined( REPLAY_ENABLED )
|
|
static bool net_noreplay = false; // disable Replay support
|
|
#endif
|
|
static bool net_dedicated = false; // true is dedicated system
|
|
static bool net_dedicatedForXbox = false; // true is dedicated system serving xbox
|
|
static bool net_dedicatedForXboxInsecure = false; // true if dedicated system serving insecure xbox
|
|
static int net_error = 0; // global error code updated with NET_GetLastError()
|
|
|
|
static int g_nFakeSocketHandle = 0; // for when we are only using Steam. Need a fake socket handle.
|
|
|
|
volatile int g_NetChannelsRefreshCounter = 0;
|
|
static CUtlVectorMT< CUtlVector< CNetChan* > > s_NetChannels;
|
|
static CUtlVectorMT< CUtlVector< pendingsocket_t > > s_PendingSockets;
|
|
|
|
CTSQueue<loopback_t *> s_LoopBacks[LOOPBACK_SOCKETS];
|
|
static netpacket_t* s_pLagData[MAX_SOCKETS]; // List of lag structures, if fakelag is set.
|
|
|
|
ISteamDatagramTransportGameserver *g_pSteamDatagramGameserver = nullptr;
|
|
ISteamDatagramTransportClient *g_pSteamDatagramClient = nullptr;
|
|
ns_address g_addrSteamDatagramProxiedGameServer;
|
|
|
|
static void CloseSteamDatagramClientConnection()
|
|
{
|
|
if ( g_pSteamDatagramClient )
|
|
{
|
|
g_pSteamDatagramClient->Close();
|
|
g_pSteamDatagramClient = nullptr;
|
|
}
|
|
g_addrSteamDatagramProxiedGameServer.Clear();
|
|
}
|
|
|
|
unsigned short NET_HostToNetShort( unsigned short us_in )
|
|
{
|
|
return htons( us_in );
|
|
}
|
|
|
|
unsigned short NET_NetToHostShort( unsigned short us_in )
|
|
{
|
|
return ntohs( us_in );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *s -
|
|
// *sadr -
|
|
// Output : bool NET_StringToSockaddr
|
|
//-----------------------------------------------------------------------------
|
|
bool NET_StringToSockaddr( const char *s, struct sockaddr *sadr )
|
|
{
|
|
char *colon;
|
|
char copy[128];
|
|
Q_memset (sadr, 0, sizeof(*sadr));
|
|
((struct sockaddr_in *)sadr)->sin_family = AF_INET;
|
|
((struct sockaddr_in *)sadr)->sin_port = 0;
|
|
|
|
Q_strncpy (copy, s, sizeof( copy ) );
|
|
// strip off a trailing :port if present
|
|
for (colon = copy ; *colon ; colon++)
|
|
{
|
|
if (*colon == ':')
|
|
{
|
|
*colon = 0;
|
|
((struct sockaddr_in *)sadr)->sin_port = NET_HostToNetShort((short)atoi(colon+1));
|
|
}
|
|
}
|
|
|
|
if (copy[0] >= '0' && copy[0] <= '9' && Q_strstr( copy, "." ) )
|
|
{
|
|
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy);
|
|
}
|
|
else
|
|
{
|
|
if ( net_nodns )
|
|
return false; // DNS names disabled
|
|
|
|
struct hostent *h;
|
|
if ( (h = gethostbyname(copy)) == NULL )
|
|
return false;
|
|
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int NET_GetLastError( void )
|
|
{
|
|
#if defined( _WIN32 )
|
|
net_error = WSAGetLastError();
|
|
#else
|
|
net_error = errno;
|
|
#endif
|
|
return net_error;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_ClearLaggedList
|
|
|
|
==================
|
|
*/
|
|
void NET_ClearLaggedList(netpacket_t **pList)
|
|
{
|
|
netpacket_t * p = (*pList);
|
|
|
|
while ( p )
|
|
{
|
|
netpacket_t * n = p->pNext;
|
|
|
|
if ( p->data )
|
|
{
|
|
delete[] p->data;
|
|
p->data = NULL;
|
|
}
|
|
delete p;
|
|
p = n;
|
|
}
|
|
|
|
(*pList) = NULL;
|
|
}
|
|
|
|
void NET_ClearLagData( int sock )
|
|
{
|
|
if ( sock < MAX_SOCKETS && s_pLagData[sock] )
|
|
{
|
|
NET_ClearLaggedList( &s_pLagData[sock] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
NET_StringToAdr
|
|
|
|
localhost
|
|
idnewt
|
|
idnewt:28000
|
|
192.246.40.70
|
|
192.246.40.70:28000
|
|
=============
|
|
*/
|
|
bool NET_StringToAdr ( const char *s, netadr_t *a)
|
|
{
|
|
return a->SetFromString( s, !net_nodns );
|
|
}
|
|
|
|
void NET_FindAllNetChannelAddresses( int socket, CUtlVector< struct sockaddr > &arrNetChans )
|
|
{
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
|
|
int numChannels = s_NetChannels.Count();
|
|
|
|
for ( int i = 0; i < numChannels; i++ )
|
|
{
|
|
CNetChan * chan = s_NetChannels[ i ];
|
|
|
|
// sockets must match
|
|
if ( socket != chan->GetSocket() )
|
|
continue;
|
|
|
|
// only ip based ones
|
|
if ( chan->GetRemoteAddress().m_AddrType != NSAT_NETADR )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// and the IP:Port address
|
|
struct sockaddr sockAddress;
|
|
chan->GetRemoteAddress().m_adr.ToSockadr( &sockAddress );
|
|
arrNetChans.AddToTail( sockAddress );
|
|
}
|
|
}
|
|
|
|
CNetChan *NET_FindNetChannel(int socket, const ns_address &adr )
|
|
{
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
|
|
int numChannels = s_NetChannels.Count();
|
|
|
|
for ( int i = 0; i < numChannels; i++ )
|
|
{
|
|
CNetChan * chan = s_NetChannels[i];
|
|
|
|
// sockets must match
|
|
if ( socket != chan->GetSocket() )
|
|
continue;
|
|
|
|
// and the address
|
|
if ( adr == chan->GetRemoteAddress() )
|
|
{
|
|
return chan; // found it
|
|
}
|
|
}
|
|
|
|
return NULL; // no channel found
|
|
}
|
|
|
|
void NET_CloseSocket( int hSocket, int sock = -1)
|
|
{
|
|
if ( !hSocket )
|
|
return;
|
|
|
|
// close socket handle
|
|
if ( !OnlyUseSteamSockets() )
|
|
{
|
|
// bugbug - shouldn't this clear net_sockets[sock].hUDP?
|
|
int ret = closesocket( hSocket );
|
|
if ( ret == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
ConMsg ("WARNING! NET_CloseSocket: %s\n", NET_ErrorString(net_error));
|
|
}
|
|
|
|
// if hSocket mapped to hTCP, clear hTCP
|
|
if ( sock >= 0 )
|
|
{
|
|
if ( net_sockets[sock].hTCP == hSocket )
|
|
{
|
|
net_sockets[sock].hTCP = 0;
|
|
net_sockets[sock].bListening = false;
|
|
}
|
|
}
|
|
|
|
// If closing client socket, make sure we don't keep trying
|
|
// to talk to server
|
|
if ( sock == NS_CLIENT )
|
|
{
|
|
CloseSteamDatagramClientConnection();
|
|
}
|
|
}
|
|
|
|
g_pSteamSocketMgr->CloseSocket( hSocket, sock );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_IPSocket
|
|
====================
|
|
*/
|
|
int NET_OpenSocket ( const char *net_interface, int& port, int protocol )
|
|
{
|
|
if ( OnlyUseSteamSockets() )
|
|
{
|
|
Msg ("WARNING: NET_OpenSocket: Not implemented - Should be using Steam\n");
|
|
return 0;
|
|
}
|
|
|
|
struct sockaddr_in address;
|
|
unsigned int opt;
|
|
int newsocket = -1;
|
|
|
|
if ( protocol == IPPROTO_TCP )
|
|
{
|
|
newsocket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
|
|
}
|
|
else // as UDP or VDP
|
|
{
|
|
newsocket = socket( PF_INET, SOCK_DGRAM, protocol );
|
|
}
|
|
|
|
if ( newsocket == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
if ( net_error != WSAEAFNOSUPPORT )
|
|
Msg ("WARNING: NET_OpenSocket: socket failed: %s", NET_ErrorString(net_error));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
opt = 1; // make it non-blocking
|
|
int ret = ioctlsocket( newsocket, FIONBIO, (unsigned long*)&opt );
|
|
if ( ret == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: ioctl FIONBIO: %s\n", NET_ErrorString(net_error) );
|
|
}
|
|
|
|
if ( protocol == IPPROTO_TCP )
|
|
{
|
|
if ( !IsX360() ) // SO_KEEPALIVE unsupported on the 360
|
|
{
|
|
opt = 1; // set TCP options: keep TCP connection alive
|
|
ret = setsockopt( newsocket, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, sizeof(opt) );
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: setsockopt SO_KEEPALIVE: %s\n", NET_ErrorString(net_error));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
linger optlinger; // set TCP options: Does not block close waiting for unsent data to be sent
|
|
optlinger.l_linger = 0;
|
|
optlinger.l_onoff = 0;
|
|
ret = setsockopt( newsocket, SOL_SOCKET, SO_LINGER, (char *)&optlinger, sizeof(optlinger) );
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: setsockopt SO_LINGER: %s\n", NET_ErrorString(net_error));
|
|
return 0;
|
|
}
|
|
|
|
opt = 1; // set TCP options: Disables the Nagle algorithm for send coalescing.
|
|
ret = setsockopt( newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&opt, sizeof(opt) );
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: setsockopt TCP_NODELAY: %s\n", NET_ErrorString(net_error));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Set the send and receive buffer sizes for TCP sockets, and UDP sockets on Windows. Windows UDP
|
|
// sockets default to 8 KB buffers which makes dataloss very common. Linux (Ubuntu 12.04 anyway)
|
|
// UDP sockets default to 208 KB so there is no need to change the setting.
|
|
// Use net_usesocketsforloopback 1 to use sockets for listen servers for testing.
|
|
if ( protocol == IPPROTO_TCP || IsPlatformWindowsPC() )
|
|
{
|
|
const int UDP_BUFFER_SIZE = 128 * 1024; // Better than 8 KB.
|
|
const int bufferSize = ( protocol == IPPROTO_TCP ) ? NET_MAX_MESSAGE : UDP_BUFFER_SIZE;
|
|
opt = bufferSize;
|
|
ret = setsockopt( newsocket, SOL_SOCKET, SO_SNDBUF, (char *)&opt, sizeof(opt) );
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: setsockopt SO_SNDBUF: %s\n", NET_ErrorString(net_error));
|
|
return 0;
|
|
}
|
|
|
|
opt = bufferSize;
|
|
ret = setsockopt( newsocket, SOL_SOCKET, SO_RCVBUF, (char *)&opt, sizeof(opt) );
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: setsockopt SO_RCVBUF: %s\n", NET_ErrorString(net_error));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ( protocol == IPPROTO_TCP )
|
|
{
|
|
return newsocket; // don't bind TCP sockets by default
|
|
}
|
|
|
|
// rest is UDP only
|
|
|
|
// VDP protocol (Xbox 360 secure network) doesn't support SO_BROADCAST
|
|
if ( !IsX360() || protocol != IPPROTO_VDP )
|
|
{
|
|
opt = 1; // set UDP options: make it broadcast capable
|
|
ret = setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&opt, sizeof(opt) );
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString(net_error));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ( CommandLine()->FindParm( "-reuse" ) )
|
|
{
|
|
opt = 1; // make it reusable
|
|
ret = setsockopt( newsocket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt) );
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_OpenSocket: setsockopt SO_REUSEADDR: %s\n", NET_ErrorString(net_error));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!net_interface || !net_interface[0] || !Q_strcmp(net_interface, "localhost"))
|
|
{
|
|
address.sin_addr.s_addr = INADDR_ANY;
|
|
}
|
|
else
|
|
{
|
|
NET_StringToSockaddr (net_interface, (struct sockaddr *)&address);
|
|
}
|
|
|
|
address.sin_family = AF_INET;
|
|
|
|
int port_offset; // try binding socket to port, try next 10 is port is already used
|
|
|
|
int nNumTries = PORT_TRY_MAX;
|
|
if ( IsChildProcess() )
|
|
nNumTries = PORT_TRY_MAX_FORKED;
|
|
|
|
// Add support for "+net_port_try" argument to override on command line
|
|
int nCommandLineOverrideNumTries = nNumTries;
|
|
nCommandLineOverrideNumTries = CommandLine()->ParmValue( "+net_port_try", nCommandLineOverrideNumTries );
|
|
nCommandLineOverrideNumTries = CommandLine()->ParmValue( "-net_port_try", nCommandLineOverrideNumTries );
|
|
if ( ( nCommandLineOverrideNumTries > 0 ) && ( nCommandLineOverrideNumTries < nNumTries ) )
|
|
nNumTries = nCommandLineOverrideNumTries;
|
|
|
|
for ( port_offset = 0; port_offset < nNumTries; port_offset++ )
|
|
{
|
|
if ( port == PORT_ANY )
|
|
{
|
|
address.sin_port = 0; // = INADDR_ANY
|
|
}
|
|
else
|
|
{
|
|
address.sin_port = NET_HostToNetShort((short)( port + port_offset ));
|
|
}
|
|
|
|
ret = bind( newsocket, (struct sockaddr *)&address, sizeof(address) );
|
|
if ( ret != -1 )
|
|
{
|
|
if ( port != PORT_ANY && port_offset != 0 )
|
|
{
|
|
port += port_offset; // update port
|
|
ConDMsg( "Socket bound to non-default port %i because original port was already in use.\n", port );
|
|
}
|
|
break;
|
|
}
|
|
|
|
NET_GetLastError();
|
|
|
|
if ( port == PORT_ANY || net_error != WSAEADDRINUSE )
|
|
{
|
|
Msg ("WARNING: NET_OpenSocket: bind: %s\n", NET_ErrorString(net_error));
|
|
NET_CloseSocket(newsocket,-1);
|
|
return 0;
|
|
}
|
|
|
|
// Try next port
|
|
}
|
|
|
|
if ( port_offset == nNumTries )
|
|
{
|
|
Msg( "WARNING: UDP_OpenSocket: unable to bind socket\n" );
|
|
NET_CloseSocket( newsocket,-1 );
|
|
return 0;
|
|
}
|
|
|
|
return newsocket;
|
|
}
|
|
|
|
int NET_ConnectSocket( int sock, const netadr_t &addr )
|
|
{
|
|
Assert( (sock >= 0) && (sock < net_sockets.Count()) );
|
|
|
|
netsocket_t *netsock = &net_sockets[sock];
|
|
|
|
if ( netsock->hTCP )
|
|
{
|
|
NET_CloseSocket( netsock->hTCP, sock );
|
|
}
|
|
|
|
if ( net_notcp )
|
|
return 0;
|
|
|
|
sockaddr saddr;
|
|
|
|
addr.ToSockadr( &saddr );
|
|
|
|
int anyport = PORT_ANY;
|
|
|
|
netsock->hTCP = NET_OpenSocket( ipname.GetString(), anyport, true );
|
|
|
|
if ( !netsock->hTCP )
|
|
{
|
|
Msg( "Warning! NET_ConnectSocket failed opening socket %i, port %i.\n", sock, net_sockets[sock].nPort );
|
|
return false;
|
|
}
|
|
|
|
int ret;
|
|
ret = connect( netsock->hTCP, &saddr, sizeof(saddr) );
|
|
if ( ret == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
|
|
if ( net_error != WSAEWOULDBLOCK )
|
|
{
|
|
Msg ("NET_ConnectSocket: %s\n", NET_ErrorString( net_error ) );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return net_sockets[sock].hTCP;
|
|
}
|
|
|
|
int NET_SendStream( int nSock, const char * buf, int len, int flags )
|
|
{
|
|
if ( OnlyUseSteamSockets() )
|
|
{
|
|
Msg( "Warning! NET_SendStream called when only using Steam sockets\n" );
|
|
return 0;
|
|
}
|
|
|
|
//int ret = send( nSock, buf, len, flags );
|
|
int ret = send( nSock, buf, len, flags );
|
|
if ( ret == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
|
|
if ( net_error == WSAEWOULDBLOCK )
|
|
{
|
|
return 0; // ignore EWOULDBLOCK
|
|
}
|
|
|
|
Msg ("NET_SendStream: %s\n", NET_ErrorString( net_error ) );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int NET_ReceiveStream( int nSock, char * buf, int len, int flags )
|
|
{
|
|
if ( OnlyUseSteamSockets() )
|
|
{
|
|
Msg( "Warning! NET_ReceiveStream called when only using Steam sockets\n" );
|
|
return 0;
|
|
}
|
|
|
|
int ret = recv( nSock, buf, len, flags );
|
|
if ( ret == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
|
|
if ( net_error == WSAEWOULDBLOCK ||
|
|
net_error == WSAENOTCONN )
|
|
{
|
|
return 0; // ignore EWOULDBLOCK
|
|
}
|
|
|
|
Msg ("NET_ReceiveStream: %s\n", NET_ErrorString( net_error ) );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
INetChannel *NET_CreateNetChannel( int socket, const ns_address *adr, const char * name, INetChannelHandler * handler, const byte *pbEncryptionKey, bool bForceNewChannel )
|
|
{
|
|
CNetChan *chan = NULL;
|
|
|
|
if ( !bForceNewChannel && adr != NULL )
|
|
{
|
|
// try to find real network channel if already existing
|
|
if ( ( chan = NET_FindNetChannel( socket, *adr ) ) != NULL )
|
|
{
|
|
// channel already known, clear any old stuff before Setup wipes all
|
|
chan->Clear();
|
|
}
|
|
}
|
|
|
|
if ( !chan )
|
|
{
|
|
// create new channel
|
|
chan = new CNetChan();
|
|
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
s_NetChannels.AddToTail( chan );
|
|
}
|
|
|
|
NET_ClearLagData( socket );
|
|
|
|
// just reset and return
|
|
ns_address adrToUse;
|
|
if ( adr )
|
|
adrToUse = *adr;
|
|
else
|
|
adrToUse.Clear();
|
|
chan->Setup( socket, adrToUse, name, handler, pbEncryptionKey );
|
|
|
|
++ g_NetChannelsRefreshCounter;
|
|
|
|
return chan;
|
|
}
|
|
|
|
void NET_RemoveNetChannel(INetChannel *netchan, bool bDeleteNetChan)
|
|
{
|
|
if ( !netchan )
|
|
{
|
|
return;
|
|
}
|
|
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
if ( s_NetChannels.Find( static_cast<CNetChan*>(netchan) ) == s_NetChannels.InvalidIndex() )
|
|
{
|
|
DevMsg(1, "NET_CloseNetChannel: unknown channel.\n");
|
|
return;
|
|
}
|
|
|
|
s_NetChannels.FindAndRemove( static_cast<CNetChan*>(netchan) );
|
|
|
|
NET_ClearQueuedPacketsForChannel( netchan );
|
|
|
|
if ( bDeleteNetChan )
|
|
delete netchan;
|
|
|
|
++ g_NetChannelsRefreshCounter;
|
|
}
|
|
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
LOOPBACK BUFFERS FOR LOCAL PLAYER
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
|
|
void NET_SendLoopPacket (int sock, int length, const unsigned char *data )
|
|
{
|
|
// Never loop on anything other than client/server
|
|
if ( sock != NS_CLIENT && sock != NS_SERVER )
|
|
return;
|
|
|
|
loopback_t *loop;
|
|
|
|
if ( length > NET_MAX_PAYLOAD )
|
|
{
|
|
DevMsg( "NET_SendLoopPacket: packet too big (%i).\n", length );
|
|
return;
|
|
}
|
|
|
|
loop = new loopback_t;
|
|
|
|
if ( length <= DEF_LOOPBACK_SIZE )
|
|
{
|
|
loop->data = loop->defbuffer;
|
|
}
|
|
else
|
|
{
|
|
loop->data = new char[ length ];
|
|
}
|
|
|
|
Q_memcpy (loop->data, data, length);
|
|
loop->datalen = length;
|
|
|
|
if ( sock == NS_SERVER )
|
|
{
|
|
s_LoopBacks[NS_CLIENT].PushItem( loop );
|
|
}
|
|
else if ( sock == NS_CLIENT )
|
|
{
|
|
s_LoopBacks[NS_SERVER].PushItem( loop );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "NET_SendLoopPacket: invalid socket (%i).\n", sock );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
int NET_CountLaggedList( netpacket_t *pList )
|
|
{
|
|
int c = 0;
|
|
netpacket_t *p = pList;
|
|
|
|
while ( p )
|
|
{
|
|
c++;
|
|
p = p->pNext;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
NET_AddToLagged
|
|
|
|
===================
|
|
*/
|
|
void NET_AddToLagged( netpacket_t **pList, netpacket_t *pPacket )
|
|
{
|
|
if ( pPacket->pNext )
|
|
{
|
|
Msg("NET_AddToLagged::Packet already linked\n");
|
|
return;
|
|
}
|
|
|
|
// first copy packet
|
|
|
|
netpacket_t *newPacket = new netpacket_t;
|
|
|
|
(*newPacket) = (*pPacket); // copy packet infos
|
|
newPacket->data = new unsigned char[ pPacket->size ]; // create new data buffer
|
|
Q_memcpy( newPacket->data, pPacket->data, pPacket->size ); // copy packet data
|
|
newPacket->pNext = NULL;
|
|
|
|
// if list is empty, this is our first element
|
|
if ( (*pList) == NULL )
|
|
{
|
|
(*pList) = newPacket; // put packet in top of list
|
|
}
|
|
else
|
|
{
|
|
netpacket_t *last = (*pList);
|
|
|
|
while ( last->pNext )
|
|
{
|
|
// got to end of list
|
|
last = last->pNext;
|
|
}
|
|
|
|
// add at end
|
|
last->pNext = newPacket;
|
|
}
|
|
}
|
|
|
|
// Actual lag to use in msec
|
|
static float s_FakeLag = 0.0;
|
|
|
|
float NET_GetFakeLag()
|
|
{
|
|
return s_FakeLag;
|
|
}
|
|
|
|
// How quickly we converge to a new value for fakelag
|
|
#define FAKELAG_CONVERGE 200 // ms per second
|
|
|
|
/*
|
|
==============================
|
|
NET_AdjustLag
|
|
|
|
==============================
|
|
*/
|
|
void NET_AdjustLag( void )
|
|
{
|
|
// Already converged?
|
|
if ( fakelag.GetFloat() == s_FakeLag )
|
|
return;
|
|
|
|
static double s_LastTime = 0;
|
|
|
|
// Bound time step
|
|
|
|
float dt = clamp( net_time - s_LastTime, 0.0f, 0.2f );
|
|
|
|
s_LastTime = net_time;
|
|
|
|
// Figure out how far we have to go
|
|
float diff = fakelag.GetFloat() - s_FakeLag;
|
|
|
|
// How much can we converge this frame
|
|
float converge = FAKELAG_CONVERGE * dt;
|
|
|
|
// Last step, go the whole way
|
|
if ( converge > fabs( diff ) )
|
|
{
|
|
converge = fabs( diff );
|
|
}
|
|
|
|
// Converge toward fakelag.GetFloat()
|
|
if ( diff < 0.0 )
|
|
{
|
|
// Converge toward fakelag.GetFloat()
|
|
s_FakeLag -= converge;
|
|
}
|
|
else
|
|
{
|
|
s_FakeLag += converge;
|
|
}
|
|
}
|
|
|
|
|
|
bool NET_LagPacket (bool newdata, netpacket_t * packet)
|
|
{
|
|
static int losscount[MAX_SOCKETS];
|
|
|
|
if ( packet->source >= MAX_SOCKETS )
|
|
return newdata; // fake lag not supported for extra sockets
|
|
|
|
if ( (droppackets.GetInt() > 0) && newdata && (packet->source == NS_CLIENT) )
|
|
{
|
|
droppackets.SetValue( droppackets.GetInt() - 1 );
|
|
return false;
|
|
}
|
|
|
|
if ( fakeloss.GetFloat() && newdata )
|
|
{
|
|
losscount[packet->source]++;
|
|
|
|
if ( fakeloss.GetFloat() > 0.0f )
|
|
{
|
|
// Act like we didn't hear anything if we are going to lose the packet.
|
|
// Depends on random # generator.
|
|
if (RandomInt(0,100) <= (int)fakeloss.GetFloat())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
int ninterval;
|
|
|
|
ninterval = (int)(fabs( fakeloss.GetFloat() ) );
|
|
ninterval = MAX( 2, ninterval );
|
|
|
|
if ( !( losscount[packet->source] % ninterval ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s_FakeLag <= 0.0)
|
|
{
|
|
// Never leave any old msgs around
|
|
for ( int i=0; i<MAX_SOCKETS; i++ )
|
|
{
|
|
NET_ClearLagData( i );
|
|
}
|
|
return newdata;
|
|
}
|
|
|
|
// if new packet arrived in fakelag list
|
|
if ( newdata )
|
|
{
|
|
NET_AddToLagged( &s_pLagData[packet->source], packet );
|
|
}
|
|
|
|
// Now check the correct list and feed any message that is old enough.
|
|
netpacket_t *p = s_pLagData[packet->source]; // current packet
|
|
|
|
if ( !p )
|
|
return false; // no packet in lag list
|
|
|
|
float target = s_FakeLag;
|
|
if ( fakejitter.GetFloat() > 0.0f )
|
|
{
|
|
float maxjitter = MIN( fakejitter.GetFloat(), target * 0.5f );
|
|
target += RandomFloat( -maxjitter, maxjitter );
|
|
}
|
|
|
|
if ( (p->received + (target/1000.0f)) > net_time )
|
|
return false; // not time yet for this packet
|
|
|
|
#ifdef _DEBUG
|
|
if ( fakeshuffle.GetInt() && p->pNext )
|
|
{
|
|
if ( !RandomInt( 0, fakeshuffle.GetInt() ) )
|
|
{
|
|
// swap p and p->next
|
|
netpacket_t * t = p->pNext;
|
|
p->pNext = t->pNext;
|
|
t->pNext = p;
|
|
p = t;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// remove packet p from list (is head)
|
|
s_pLagData[packet->source] = p->pNext;
|
|
|
|
// copy & adjust content
|
|
packet->source = p->source;
|
|
packet->from = p->from;
|
|
packet->pNext = NULL; // no next
|
|
packet->received = net_time; // new time
|
|
packet->size = p->size;
|
|
packet->wiresize = p->wiresize;
|
|
packet->stream = p->stream;
|
|
|
|
Q_memcpy( packet->data, p->data, p->size );
|
|
|
|
// free lag packet
|
|
|
|
delete[] p->data;
|
|
delete p;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Calculate MAX_SPLITPACKET_SPLITS according to the smallest split size
|
|
#define MAX_SPLITPACKET_SPLITS ( NET_MAX_MESSAGE / MIN_SPLIT_SIZE )
|
|
#define SPLIT_PACKET_STALE_TIME 15.0f
|
|
|
|
class CSplitPacketEntry
|
|
{
|
|
public:
|
|
CSplitPacketEntry()
|
|
{
|
|
|
|
int i;
|
|
for ( i = 0; i < MAX_SPLITPACKET_SPLITS; i++ )
|
|
{
|
|
splitflags[ i ] = -1;
|
|
}
|
|
|
|
memset( &netsplit, 0, sizeof( netsplit ) );
|
|
lastactivetime = 0.0f;
|
|
}
|
|
|
|
public:
|
|
ns_address from;
|
|
int splitflags[ MAX_SPLITPACKET_SPLITS ];
|
|
LONGPACKET netsplit;
|
|
// host_time the last time any entry was received for this entry
|
|
float lastactivetime;
|
|
};
|
|
|
|
typedef CUtlVector< CSplitPacketEntry > vecSplitPacketEntries_t;
|
|
static CUtlVector<vecSplitPacketEntries_t> net_splitpackets;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void NET_DiscardStaleSplitpackets( const int sock )
|
|
{
|
|
if ( !net_splitpackets.IsValidIndex( sock ) )
|
|
return;
|
|
|
|
vecSplitPacketEntries_t &splitPacketEntries = net_splitpackets[sock];
|
|
int i;
|
|
for ( i = splitPacketEntries.Count() - 1; i >= 0; i-- )
|
|
{
|
|
CSplitPacketEntry *entry = &splitPacketEntries[ i ];
|
|
Assert( entry );
|
|
|
|
if ( net_time < ( entry->lastactivetime + SPLIT_PACKET_STALE_TIME ) )
|
|
continue;
|
|
|
|
splitPacketEntries.Remove( i );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *from -
|
|
// Output : CSplitPacketEntry
|
|
//-----------------------------------------------------------------------------
|
|
CSplitPacketEntry *NET_FindOrCreateSplitPacketEntry( const int sock, const ns_address &from )
|
|
{
|
|
vecSplitPacketEntries_t &splitPacketEntries = net_splitpackets[sock];
|
|
int i, count = splitPacketEntries.Count();
|
|
CSplitPacketEntry *entry = NULL;
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
entry = &splitPacketEntries[ i ];
|
|
Assert( entry );
|
|
|
|
if ( from == entry->from )
|
|
break;
|
|
}
|
|
|
|
if ( i >= count )
|
|
{
|
|
CSplitPacketEntry newentry;
|
|
newentry.from = from;
|
|
|
|
splitPacketEntries.AddToTail( newentry );
|
|
|
|
entry = &splitPacketEntries[ splitPacketEntries.Count() - 1 ];
|
|
}
|
|
|
|
Assert( entry );
|
|
return entry;
|
|
}
|
|
|
|
static const tokenset_t< ESocketIndex_t > s_SocketDescMap[] =
|
|
{
|
|
{ "cl", NS_CLIENT },
|
|
{ "sv", NS_SERVER },
|
|
#ifdef _X360
|
|
{ "Xsl", NS_X360_SYSTEMLINK },
|
|
{ "Xlb", NS_X360_LOBBY },
|
|
{ "Xtl", NS_X360_TEAMLINK },
|
|
#endif
|
|
{ "htv", NS_HLTV },
|
|
{ "htv1", NS_HLTV1 },
|
|
#if defined( REPLAY_ENABLED )
|
|
{ "rply", NS_REPLAY },
|
|
#endif
|
|
{ NULL, (ESocketIndex_t)-1 }
|
|
};
|
|
|
|
static char const *DescribeSocket( int sock )
|
|
{
|
|
return s_SocketDescMap->GetNameByToken( ( ESocketIndex_t ) sock, "??" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pData -
|
|
// size -
|
|
// *outSize -
|
|
// Output : bool
|
|
//-----------------------------------------------------------------------------
|
|
bool NET_GetLong( const int sock, netpacket_t *packet )
|
|
{
|
|
int packetNumber, packetCount, sequenceNumber, offset;
|
|
short packetID;
|
|
SPLITPACKET *pHeader;
|
|
|
|
if ( packet->size < sizeof(SPLITPACKET) )
|
|
{
|
|
Msg( "Invalid split packet length %i\n", packet->size );
|
|
return false;
|
|
}
|
|
|
|
CSplitPacketEntry *entry = NET_FindOrCreateSplitPacketEntry( sock, packet->from );
|
|
Assert( entry );
|
|
if ( !entry )
|
|
return false;
|
|
|
|
entry->lastactivetime = net_time;
|
|
Assert( packet->from.CompareAdr( entry->from ) );
|
|
|
|
pHeader = ( SPLITPACKET * )packet->data;
|
|
// pHeader is network endian correct
|
|
sequenceNumber = LittleLong( pHeader->sequenceNumber );
|
|
packetID = LittleShort( (short)pHeader->packetID );
|
|
// High byte is packet number
|
|
packetNumber = ( packetID >> 8 );
|
|
// Low byte is number of total packets
|
|
packetCount = ( packetID & 0xff );
|
|
|
|
int nSplitSizeMinusHeader = (int)LittleShort( (short)pHeader->nSplitSize );
|
|
if ( nSplitSizeMinusHeader < MIN_SPLIT_SIZE ||
|
|
nSplitSizeMinusHeader > MAX_SPLIT_SIZE )
|
|
{
|
|
Msg( "NET_GetLong: Split packet from %s with invalid split size (number %i/ count %i) where size %i is out of valid range [%d - %d ]\n",
|
|
ns_address_render( packet->from ).String(),
|
|
packetNumber,
|
|
packetCount,
|
|
nSplitSizeMinusHeader,
|
|
MIN_SPLIT_SIZE,
|
|
MAX_SPLIT_SIZE );
|
|
return false;
|
|
}
|
|
|
|
if ( packetNumber >= MAX_SPLITPACKET_SPLITS ||
|
|
packetCount > MAX_SPLITPACKET_SPLITS )
|
|
{
|
|
Msg( "NET_GetLong: Split packet from %s with too many split parts (number %i/ count %i) where %i is max count allowed\n",
|
|
ns_address_render( packet->from ).String(),
|
|
packetNumber,
|
|
packetCount,
|
|
MAX_SPLITPACKET_SPLITS );
|
|
return false;
|
|
}
|
|
|
|
// First packet in split series?
|
|
if ( entry->netsplit.currentSequence == -1 ||
|
|
sequenceNumber != entry->netsplit.currentSequence )
|
|
{
|
|
entry->netsplit.currentSequence = sequenceNumber;
|
|
entry->netsplit.splitCount = packetCount;
|
|
entry->netsplit.nExpectedSplitSize = nSplitSizeMinusHeader;
|
|
}
|
|
|
|
if ( entry->netsplit.nExpectedSplitSize != nSplitSizeMinusHeader )
|
|
{
|
|
Msg( "NET_GetLong: Split packet from %s with inconsistent split size (number %i/ count %i) where size %i not equal to initial size of %i\n",
|
|
ns_address_render( packet->from ).String(),
|
|
packetNumber,
|
|
packetCount,
|
|
nSplitSizeMinusHeader,
|
|
entry->netsplit.nExpectedSplitSize
|
|
);
|
|
return false;
|
|
}
|
|
|
|
int size = packet->size - sizeof(SPLITPACKET);
|
|
|
|
if ( entry->splitflags[ packetNumber ] != sequenceNumber )
|
|
{
|
|
// Last packet in sequence? set size
|
|
if ( packetNumber == (packetCount-1) )
|
|
{
|
|
entry->netsplit.totalSize = (packetCount-1) * nSplitSizeMinusHeader + size;
|
|
}
|
|
|
|
entry->netsplit.splitCount--; // Count packet
|
|
entry->splitflags[ packetNumber ] = sequenceNumber;
|
|
|
|
if ( net_showsplits.GetInt() && net_showsplits.GetInt() != 3 )
|
|
{
|
|
Msg( "<-- [%s] Split packet %4i/%4i seq %5i size %4i mtu %4i from %s\n",
|
|
DescribeSocket( sock ),
|
|
packetNumber + 1,
|
|
packetCount,
|
|
sequenceNumber,
|
|
size,
|
|
nSplitSizeMinusHeader + sizeof( SPLITPACKET ),
|
|
ns_address_render( packet->from ).String() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Msg( "NET_GetLong: Ignoring duplicated split packet %i of %i ( %i bytes ) from %s\n", packetNumber + 1, packetCount, size, ns_address_render( packet->from ).String() );
|
|
}
|
|
|
|
|
|
// Copy the incoming data to the appropriate place in the buffer
|
|
offset = (packetNumber * nSplitSizeMinusHeader);
|
|
memcpy( entry->netsplit.buffer + offset, packet->data + sizeof(SPLITPACKET), size );
|
|
|
|
// Have we received all of the pieces to the packet?
|
|
if ( entry->netsplit.splitCount <= 0 )
|
|
{
|
|
entry->netsplit.currentSequence = -1; // Clear packet
|
|
if ( entry->netsplit.totalSize > sizeof(entry->netsplit.buffer) )
|
|
{
|
|
Msg("Split packet too large! %d bytes from %s\n", entry->netsplit.totalSize, ns_address_render( packet->from ).String() );
|
|
return false;
|
|
}
|
|
|
|
Q_memcpy( packet->data, entry->netsplit.buffer, entry->netsplit.totalSize );
|
|
packet->size = entry->netsplit.totalSize;
|
|
packet->wiresize = entry->netsplit.totalSize;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool NET_GetLoopPacket ( netpacket_t * packet )
|
|
{
|
|
Assert ( packet );
|
|
|
|
loopback_t *loop = NULL;
|
|
|
|
if ( packet->source > NS_SERVER )
|
|
return false;
|
|
|
|
if ( !s_LoopBacks[packet->source].PopItem( &loop ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (loop->datalen == 0)
|
|
{
|
|
// no packet in loopback buffer
|
|
delete loop;
|
|
return ( NET_LagPacket( false, packet ) );
|
|
}
|
|
|
|
// copy data from loopback buffer to packet
|
|
packet->from.SetAddrType( NSAT_NETADR );
|
|
packet->from.m_adr.SetType( NA_LOOPBACK );
|
|
packet->size = loop->datalen;
|
|
packet->wiresize = loop->datalen;
|
|
Q_memcpy ( packet->data, loop->data, packet->size );
|
|
|
|
loop->datalen = 0; // buffer is avalibale again
|
|
|
|
if ( loop->data != loop->defbuffer )
|
|
{
|
|
delete loop->data;
|
|
loop->data = loop->defbuffer;
|
|
}
|
|
|
|
delete loop;
|
|
|
|
// allow lag system to modify packet
|
|
return ( NET_LagPacket( true, packet ) );
|
|
}
|
|
|
|
static int NET_ReceiveRawPacket( int sock, void *buf, int len, ns_address *from )
|
|
{
|
|
int net_socket = net_sockets[ sock ].hUDP;
|
|
|
|
int ret = g_pSteamSocketMgr->recvfrom(net_socket, (char*)buf, len, 0, from );
|
|
if ( ret > 0 )
|
|
return ret;
|
|
|
|
// Still nothing? Check proxied clients
|
|
if ( g_pSteamDatagramGameserver )
|
|
{
|
|
CSteamID remoteSteamID;
|
|
uint64 usecTimeRecv;
|
|
if ( sock == NS_SERVER )
|
|
{
|
|
ret = g_pSteamDatagramGameserver->RecvDatagram( buf, len, &remoteSteamID, &usecTimeRecv, STEAM_P2P_GAME_SERVER );
|
|
if ( ret > 0 )
|
|
{
|
|
from->SetFromSteamID( remoteSteamID, STEAM_P2P_GAME_CLIENT );
|
|
from->m_AddrType = NSAT_PROXIED_CLIENT;
|
|
return ret;
|
|
}
|
|
}
|
|
else if ( sock == NS_HLTV )
|
|
{
|
|
ret = g_pSteamDatagramGameserver->RecvDatagram( buf, len, &remoteSteamID, &usecTimeRecv, STEAM_P2P_HLTV );
|
|
if ( ret > 0 )
|
|
{
|
|
from->SetFromSteamID( remoteSteamID, STEAM_P2P_GAME_CLIENT );
|
|
from->m_AddrType = NSAT_PROXIED_CLIENT;
|
|
return ret;
|
|
}
|
|
}
|
|
else if ( sock == NS_HLTV1 )
|
|
{
|
|
ret = g_pSteamDatagramGameserver->RecvDatagram( buf, len, &remoteSteamID, &usecTimeRecv, STEAM_P2P_HLTV1 );
|
|
if ( ret > 0 )
|
|
{
|
|
from->SetFromSteamID( remoteSteamID, STEAM_P2P_GAME_CLIENT );
|
|
from->m_AddrType = NSAT_PROXIED_CLIENT;
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Still nothing? Check proxied server
|
|
#ifndef DEDICATED
|
|
if ( sock == NS_CLIENT && ret <= 0 && g_pSteamDatagramClient && g_addrSteamDatagramProxiedGameServer.IsValid() )
|
|
{
|
|
//CSteamID remoteSteamID;
|
|
uint64 usecTimeRecv;
|
|
int ret = g_pSteamDatagramClient->RecvDatagram( buf, len, &usecTimeRecv, STEAM_P2P_GAME_CLIENT );
|
|
if ( ret > 0 )
|
|
{
|
|
*from = g_addrSteamDatagramProxiedGameServer;
|
|
//pReceiveData->usTime = usecTimeRecv;
|
|
return ret;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// nothing
|
|
return 0;
|
|
}
|
|
|
|
static bool NET_ReceiveDatagram_Helper( const int sock, netpacket_t * packet, bool &bNoMorePacketsInSocketPipe )
|
|
{
|
|
Assert ( packet );
|
|
Assert ( net_multiplayer );
|
|
|
|
#if defined( _DEBUG ) && !defined( _PS3 )
|
|
if ( recvpackets.GetInt() >= 0 )
|
|
{
|
|
unsigned long bytes;
|
|
|
|
int net_socket = net_sockets[packet->source].hUDP;
|
|
ioctlsocket( net_socket, FIONREAD, &bytes );
|
|
|
|
if ( bytes <= 0 )
|
|
{
|
|
bNoMorePacketsInSocketPipe = true;
|
|
return false;
|
|
}
|
|
|
|
if ( recvpackets.GetInt() == 0 )
|
|
{
|
|
bNoMorePacketsInSocketPipe = true;
|
|
return false;
|
|
}
|
|
|
|
recvpackets.SetValue( recvpackets.GetInt() - 1 );
|
|
}
|
|
#endif
|
|
|
|
int ret = NET_ReceiveRawPacket( sock, packet->data, NET_MAX_MESSAGE, &packet->from );
|
|
bNoMorePacketsInSocketPipe = ( ret <= 0 );
|
|
if ( ret > 0 )
|
|
{
|
|
packet->wiresize = ret;
|
|
|
|
MEM_ALLOC_CREDIT();
|
|
CUtlMemoryFixedGrowable< byte, NET_COMPRESSION_STACKBUF_SIZE > bufVoice( NET_COMPRESSION_STACKBUF_SIZE );
|
|
|
|
unsigned int nVoiceBits = 0u;
|
|
|
|
if ( IsX360() || net_dedicatedForXbox )
|
|
{
|
|
// X360TBD: Check for voice data and forward it to XAudio
|
|
// For now, just pull off the 2-byte VDP header and shift the data
|
|
unsigned short nDataBytes = ( *( unsigned short * )packet->data );
|
|
|
|
// 0xFFFF check is necessary because our LAN is broadcasting Source Engine Query requests
|
|
// which uses the out of band header, 0xFFFFFFFF, so it's not an XBox VDP packet.
|
|
if ( nDataBytes != 0xFFFF )
|
|
{
|
|
Assert( nDataBytes > 0 && nDataBytes <= ret );
|
|
|
|
int nVoiceBytes = ret - nDataBytes - 2;
|
|
if ( nVoiceBytes > 0 )
|
|
{
|
|
if ( voice_verbose.GetBool() )
|
|
{
|
|
Msg( "* NET_ReceiveDatagram: receiving voice from %s (%d bytes)\n", ns_address_render( packet->from ).String(), nVoiceBytes );
|
|
}
|
|
|
|
byte *pVoice = (byte *)packet->data + 2 + nDataBytes;
|
|
|
|
nVoiceBits = (unsigned int)LittleShort( *( unsigned short *)pVoice );
|
|
unsigned int nExpectedVoiceBytes = Bits2Bytes( nVoiceBits );
|
|
pVoice += sizeof( unsigned short );
|
|
|
|
CLZSS lzss;
|
|
if ( lzss.IsCompressed( pVoice ) )
|
|
{
|
|
unsigned int unDecompressedVoice = lzss.GetActualSize( pVoice );
|
|
if ( unDecompressedVoice != nExpectedVoiceBytes )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bufVoice.EnsureCapacity( unDecompressedVoice );
|
|
|
|
// Decompress it
|
|
unsigned int unCheck = lzss.SafeUncompress( pVoice, bufVoice.Base(), unDecompressedVoice );
|
|
if ( unCheck != unDecompressedVoice )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
nVoiceBytes = unDecompressedVoice;
|
|
}
|
|
else
|
|
{
|
|
bufVoice.EnsureCapacity( nVoiceBytes );
|
|
Q_memcpy( bufVoice.Base(), pVoice, nVoiceBytes );
|
|
}
|
|
}
|
|
|
|
Q_memmove( packet->data, &packet->data[2], nDataBytes );
|
|
|
|
ret = nDataBytes;
|
|
}
|
|
}
|
|
|
|
packet->size = ret;
|
|
|
|
|
|
if ( ret < NET_MAX_MESSAGE )
|
|
{
|
|
// Check for split message
|
|
if ( LittleLong( *(int *)packet->data ) == NET_HEADER_FLAG_SPLITPACKET )
|
|
{
|
|
if ( !NET_GetLong( sock, packet ) )
|
|
return false;
|
|
}
|
|
|
|
// Now check if the data on the wire is encrypted?
|
|
CUtlMemoryFixedGrowable< byte, NET_COMPRESSION_STACKBUF_SIZE > memDecryptedAll( NET_COMPRESSION_STACKBUF_SIZE );
|
|
if ( LittleLong( *(int *)packet->data ) != CONNECTIONLESS_HEADER )
|
|
{
|
|
// If the channel has encryption then decrypt the packet
|
|
CNetChan * chan = NET_FindNetChannel( sock, packet->from );
|
|
if ( !chan )
|
|
return false; // this is not an error during connect/disconnect, but non-connectionless packets must have a channel to process anyways
|
|
|
|
if ( const unsigned char *pubEncryptionKey = chan->GetChannelEncryptionKey() )
|
|
{
|
|
// Decrypt the packet
|
|
IceKey iceKey( 2 );
|
|
iceKey.set( pubEncryptionKey );
|
|
|
|
if ( ( packet->size % iceKey.blockSize() ) == 0 )
|
|
{
|
|
// Decrypt the message
|
|
memDecryptedAll.EnsureCapacity( packet->size );
|
|
unsigned char *pchCryptoBuffer = ( unsigned char * ) stackalloc( iceKey.blockSize() );
|
|
for ( int k = 0; k < ( int ) packet->size; k += iceKey.blockSize() )
|
|
{
|
|
iceKey.decrypt( ( const unsigned char * ) ( packet->data + k ), pchCryptoBuffer );
|
|
Q_memcpy( memDecryptedAll.Base() + k, pchCryptoBuffer, iceKey.blockSize() );
|
|
}
|
|
|
|
// Check how much random fudge we have
|
|
int numRandomFudgeBytes = *memDecryptedAll.Base();
|
|
if ( ( numRandomFudgeBytes > 0 ) && ( int( numRandomFudgeBytes + 1 + sizeof( int32 ) ) < packet->size ) )
|
|
{
|
|
// Fetch the size of the encrypted message
|
|
int32 numBytesWrittenWire = 0;
|
|
Q_memcpy( &numBytesWrittenWire, memDecryptedAll.Base() + 1 + numRandomFudgeBytes, sizeof( int32 ) );
|
|
int32 const numBytesWritten = BigLong( numBytesWrittenWire ); // byteswap from the wire
|
|
|
|
// Make sure the total size of the message matches the expectations
|
|
if ( int( numRandomFudgeBytes + 1 + sizeof( int32 ) +numBytesWritten ) == packet->size )
|
|
{
|
|
// Fix the packet to point at decrypted data!
|
|
packet->size = numBytesWritten;
|
|
Q_memcpy( packet->data, memDecryptedAll.Base() + 1 + numRandomFudgeBytes + sizeof( int32 ), packet->size );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Next check for compressed message
|
|
if ( LittleLong( *(int *)packet->data) == NET_HEADER_FLAG_COMPRESSEDPACKET )
|
|
{
|
|
byte *pCompressedData = packet->data + sizeof( unsigned int );
|
|
|
|
CLZSS lzss;
|
|
// Decompress
|
|
int actualSize = lzss.GetActualSize( pCompressedData );
|
|
if ( actualSize <= 0 || actualSize > NET_MAX_PAYLOAD )
|
|
return false;
|
|
|
|
MEM_ALLOC_CREDIT();
|
|
CUtlMemoryFixedGrowable< byte, NET_COMPRESSION_STACKBUF_SIZE > memDecompressed( NET_COMPRESSION_STACKBUF_SIZE );
|
|
memDecompressed.EnsureCapacity( actualSize );
|
|
|
|
unsigned int uDecompressedSize = lzss.SafeUncompress( pCompressedData, memDecompressed.Base(), actualSize );
|
|
if ( uDecompressedSize == 0 || ((unsigned int)actualSize) != uDecompressedSize )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// packet->wiresize is already set
|
|
Q_memcpy( packet->data, memDecompressed.Base(), uDecompressedSize );
|
|
|
|
packet->size = uDecompressedSize;
|
|
}
|
|
|
|
if ( nVoiceBits > 0 )
|
|
{
|
|
// 9th byte is flag byte
|
|
byte flagByte = *( (byte *)packet->data + sizeof( unsigned int ) + sizeof( unsigned int ) );
|
|
unsigned int unPacketBits = packet->size << 3;
|
|
int nPadBits = DECODE_PAD_BITS( flagByte );
|
|
unPacketBits -= nPadBits;
|
|
|
|
//check the CRC value in the original data packet
|
|
if( ShouldChecksumPackets() )
|
|
{
|
|
//we still want to honor the old checksum so we need to do it here instead of the usual location in CNetChan::ProcessPacketHeader
|
|
//If the layout of the header ever changes this code will need to be updated.
|
|
int checkSumByteOffset = sizeof( unsigned int ) + sizeof( unsigned int ) + sizeof( byte );
|
|
packet->message.Seek( checkSumByteOffset << 3 );
|
|
int oldChecksum = packet->message.ReadUBitLong( 16 );
|
|
packet->message.Seek(0);
|
|
|
|
int rawDataByteOffset = sizeof( unsigned int ) + sizeof( unsigned int ) + sizeof( byte ) + sizeof( unsigned short );
|
|
void *pvData = packet->data + rawDataByteOffset;
|
|
int nCheckSumBytes = packet->size - rawDataByteOffset;
|
|
if ( nCheckSumBytes <= 0 || nCheckSumBytes > NET_MAX_PAYLOAD )
|
|
{
|
|
ConMsg ( "corrupted packet detected (checksumbytes %d)\n", nCheckSumBytes );
|
|
return false;
|
|
}
|
|
|
|
unsigned short usDataCheckSum = BufferToShortChecksum( pvData, nCheckSumBytes );
|
|
if ( usDataCheckSum != oldChecksum )
|
|
{
|
|
ConMsg ( "corrupted packet detected\n" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// create the combined gamedata + voicedata packet
|
|
bf_write fixup;
|
|
fixup.SetDebugName( "X360 Fixup" );
|
|
fixup.StartWriting( packet->data, NET_MAX_MESSAGE, unPacketBits );
|
|
fixup.WriteBits( bufVoice.Base(), nVoiceBits );
|
|
|
|
// Make sure we have enough bits to read a final net_NOP opcode before compressing
|
|
int nRemainingBits = fixup.GetNumBitsWritten() % 8;
|
|
if ( nRemainingBits > 0 && nRemainingBits <= (8-NETMSG_TYPE_BITS) )
|
|
{
|
|
CNETMsg_NOP_t nop;
|
|
nop.WriteToBuffer( fixup );
|
|
}
|
|
|
|
packet->size = fixup.GetNumBytesWritten();
|
|
|
|
//recompute the new CRC value in the header.
|
|
if( ShouldChecksumPackets() )
|
|
{
|
|
//CNetChan::ProcessPacketHeader will still be looking for the checksum so we need to generate one that will keep it happy.
|
|
int checkSumByteOffset = sizeof( unsigned int ) + sizeof( unsigned int ) + sizeof( byte );
|
|
fixup.SeekToBit( checkSumByteOffset << 3 );//seek to bit position of checksum
|
|
|
|
int rawDataByteOffset = sizeof( unsigned int ) + sizeof( unsigned int ) + sizeof( byte ) + sizeof( unsigned short );
|
|
void *pvData = packet->data + rawDataByteOffset;
|
|
int nCheckSumBytes = packet->size - rawDataByteOffset;
|
|
unsigned short newChecksum = BufferToShortChecksum( pvData, nCheckSumBytes );
|
|
|
|
fixup.WriteUBitLong( newChecksum, 16 );
|
|
}
|
|
}
|
|
|
|
return NET_LagPacket( true, packet );
|
|
}
|
|
else
|
|
{
|
|
ConDMsg ( "NET_ReceiveDatagram: Oversize packet from %s\n", ns_address_render( packet->from ).String() );
|
|
}
|
|
}
|
|
else if ( ret == -1 ) // error?
|
|
{
|
|
NET_GetLastError();
|
|
|
|
switch ( net_error )
|
|
{
|
|
case WSAEWOULDBLOCK:
|
|
case WSAECONNRESET:
|
|
case WSAECONNREFUSED:
|
|
break;
|
|
case WSAEMSGSIZE:
|
|
ConDMsg ("NET_ReceivePacket: %s\n", NET_ErrorString(net_error));
|
|
break;
|
|
default:
|
|
// Let's continue even after errors
|
|
ConDMsg ("NET_ReceivePacket: %s\n", NET_ErrorString(net_error));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#define NET_WS_PACKET_PROFILE 0
|
|
#if NET_WS_PACKET_PROFILE
|
|
static uint64 g_nSockUDPTotalGood = 0;
|
|
static uint64 g_nSockUDPTotalBad = 0;
|
|
static uint64 g_nSockUDPTotalProcess = 0;
|
|
#define NET_WS_PACKET_STAT( sock, var ) if ( sv.IsActive() ) { if ( sock == NS_SERVER ) ++ var; } else { if ( sock == NS_CLIENT ) ++ var; }
|
|
CON_COMMAND( net_show_packet_stats, "Displays UDP packet statistics and resets the counters\n" )
|
|
{
|
|
Msg( "UDP processed: %llu pumps, %llu good pkts, %llu bad pkts.\n", g_nSockUDPTotalProcess, g_nSockUDPTotalGood, g_nSockUDPTotalBad );
|
|
Msg( "UDP rate: %.6f good pkt/pmp, %.6f bad pkt/pmp.\n", double( g_nSockUDPTotalGood )/double( MAX( g_nSockUDPTotalProcess, 1 ) ), double( g_nSockUDPTotalBad )/double( MAX( g_nSockUDPTotalProcess, 1 ) ) );
|
|
g_nSockUDPTotalGood = 0;
|
|
g_nSockUDPTotalBad = 0;
|
|
g_nSockUDPTotalProcess = 0;
|
|
}
|
|
#else
|
|
#define NET_WS_PACKET_STAT( sock, var )
|
|
#endif
|
|
bool NET_ReceiveDatagram ( const int sock, netpacket_t * packet )
|
|
{
|
|
for ( ;; )
|
|
{
|
|
bool bNoMorePacketsInSocketPipe = true;
|
|
bool bFoundGoodPacket = NET_ReceiveDatagram_Helper( sock, packet, bNoMorePacketsInSocketPipe );
|
|
if ( bFoundGoodPacket )
|
|
{
|
|
NET_WS_PACKET_STAT( sock, g_nSockUDPTotalGood );
|
|
return true;
|
|
}
|
|
if ( bNoMorePacketsInSocketPipe )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
NET_WS_PACKET_STAT( sock, g_nSockUDPTotalBad );
|
|
// continue, this was a bad code that in old networking code would cause a packet processing hitch
|
|
}
|
|
}
|
|
}
|
|
|
|
netpacket_t *NET_GetPacket (int sock, byte *scratch )
|
|
{
|
|
if ( !net_packets.IsValidIndex( sock ) )
|
|
return NULL;
|
|
|
|
// Each socket has its own netpacket to allow multithreading
|
|
netpacket_t &inpacket = net_packets[sock];
|
|
|
|
NET_AdjustLag();
|
|
NET_DiscardStaleSplitpackets( sock );
|
|
|
|
// setup new packet
|
|
inpacket.from.Clear();
|
|
inpacket.received = net_time;
|
|
inpacket.source = sock;
|
|
inpacket.data = scratch;
|
|
inpacket.size = 0;
|
|
inpacket.wiresize = 0;
|
|
inpacket.pNext = NULL;
|
|
inpacket.message.SetDebugName("inpacket.message");
|
|
|
|
// Check loopback first
|
|
if ( !NET_GetLoopPacket( &inpacket ) )
|
|
{
|
|
#ifdef PORTAL2
|
|
extern IVEngineClient *engineClient;
|
|
// PORTAL2-specific hack for console perf - don't waste time reading from the actual socket (expensive Steam code)
|
|
if ( !NET_IsMultiplayer() || engineClient->IsSplitScreenActive()
|
|
|| ( !IsGameConsole() && sv.IsActive() && !sv. IsMultiplayer() ) )
|
|
#else // PORTAL2
|
|
if ( !NET_IsMultiplayer() )
|
|
#endif // !PORTAL2
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// then check UDP data
|
|
if ( !NET_ReceiveDatagram( sock, &inpacket ) )
|
|
{
|
|
// at last check if the lag system has a packet for us
|
|
if ( !NET_LagPacket (false, &inpacket) )
|
|
{
|
|
return NULL; // we don't have any new packet
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert ( inpacket.size );
|
|
|
|
#ifdef _DEBUG
|
|
if ( fakenoise.GetInt() > 0 )
|
|
{
|
|
COM_AddNoise( inpacket.data, inpacket.size, fakenoise.GetInt() );
|
|
}
|
|
#endif
|
|
|
|
// prepare bitbuffer for reading packet with new size
|
|
inpacket.message.StartReading( inpacket.data, inpacket.size );
|
|
|
|
return &inpacket;
|
|
}
|
|
|
|
void NET_ProcessPending( void )
|
|
{
|
|
AUTO_LOCK_FM( s_PendingSockets );
|
|
for ( int i=0; i<s_PendingSockets.Count();i++ )
|
|
{
|
|
pendingsocket_t * psock = &s_PendingSockets[i];
|
|
|
|
ALIGN4 char headerBuf[5] ALIGN4_POST;
|
|
|
|
if ( (net_time - psock->time) > TCP_CONNECT_TIMEOUT )
|
|
{
|
|
NET_CloseSocket( psock->newsock );
|
|
s_PendingSockets.Remove( i );
|
|
continue;
|
|
}
|
|
|
|
int ret = NET_ReceiveStream( psock->newsock, headerBuf, sizeof(headerBuf), 0 );
|
|
|
|
if ( ret == 0 )
|
|
{
|
|
continue; // nothing received
|
|
}
|
|
else if ( ret == -1 )
|
|
{
|
|
NET_CloseSocket( psock->newsock );
|
|
s_PendingSockets.Remove( i );
|
|
continue; // connection closed somehow
|
|
}
|
|
|
|
bf_read header( headerBuf, sizeof(headerBuf) );
|
|
|
|
int cmd = header.ReadByte();
|
|
unsigned long challengeNr = header.ReadLong();
|
|
bool bOK = false;
|
|
|
|
if ( cmd == STREAM_CMD_ACKN )
|
|
{
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
for ( int j = 0; j < s_NetChannels.Count(); j++ )
|
|
{
|
|
CNetChan * chan = s_NetChannels[j];
|
|
|
|
if ( chan->GetSocket() != psock->netsock )
|
|
continue;
|
|
|
|
if ( challengeNr == chan->GetChallengeNr() && !chan->m_StreamSocket )
|
|
{
|
|
if ( psock->addr.CompareAdr( chan->remote_address.AsType<netadr_t>(), true ) )
|
|
{
|
|
chan->m_StreamSocket = psock->newsock;
|
|
chan->m_StreamActive = true;
|
|
|
|
chan->ResetStreaming();
|
|
|
|
bOK = true;
|
|
|
|
if ( net_showtcp.GetInt() )
|
|
{
|
|
Msg ("TCP <- %s: connection accepted\n", psock->addr.ToString() );
|
|
}
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Msg ("TCP <- %s: IP address mismatch.\n", psock->addr.ToString() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !bOK )
|
|
{
|
|
Msg ("TCP <- %s: invalid connection request.\n", psock->addr.ToString() );
|
|
NET_CloseSocket( psock->newsock );
|
|
}
|
|
|
|
s_PendingSockets.Remove( i );
|
|
}
|
|
}
|
|
|
|
void NET_ProcessListen(int sock)
|
|
{
|
|
netsocket_t * netsock = &net_sockets[sock];
|
|
|
|
if ( !netsock->bListening || OnlyUseSteamSockets() )
|
|
return;
|
|
|
|
sockaddr sa;
|
|
int nLengthAddr = sizeof(sa);
|
|
|
|
int newSocket = accept( netsock->hTCP, &sa, (socklen_t*)&nLengthAddr );
|
|
if ( newSocket == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
|
|
if ( net_error != WSAEWOULDBLOCK )
|
|
{
|
|
ConDMsg ("NET_ThreadListen: %s\n", NET_ErrorString(net_error));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// new connection TCP request, put in pending queue
|
|
|
|
pendingsocket_t psock;
|
|
|
|
psock.newsock = newSocket;
|
|
psock.netsock = sock;
|
|
psock.addr.SetFromSockadr( &sa );
|
|
psock.time = net_time;
|
|
|
|
AUTO_LOCK_FM( s_PendingSockets );
|
|
s_PendingSockets.AddToTail( psock );
|
|
|
|
// tell client to send challenge number to identify
|
|
|
|
char authcmd = STREAM_CMD_AUTH;
|
|
|
|
NET_SendStream( newSocket, &authcmd, 1 , 0 );
|
|
|
|
if ( net_showtcp.GetInt() )
|
|
{
|
|
Msg ("TCP <- %s: connection request.\n", psock.addr.ToString() );
|
|
}
|
|
}
|
|
|
|
void NET_ProcessSocket( int sock, IConnectionlessPacketHandler *handler )
|
|
{
|
|
class CAutoNetProcessSocketStartEnd
|
|
{
|
|
public:
|
|
CAutoNetProcessSocketStartEnd( int sock ) : m_sock( sock )
|
|
{
|
|
extern void On_NET_ProcessSocket_Start( int hUDP, int sock );
|
|
On_NET_ProcessSocket_Start( net_sockets[m_sock].hUDP, m_sock );
|
|
NET_WS_PACKET_STAT( sock, g_nSockUDPTotalProcess );
|
|
}
|
|
~CAutoNetProcessSocketStartEnd()
|
|
{
|
|
extern void On_NET_ProcessSocket_End( int hUDP, int sock );
|
|
On_NET_ProcessSocket_End( net_sockets[m_sock].hUDP, m_sock );
|
|
}
|
|
private:
|
|
int m_sock;
|
|
}
|
|
autoThreadSockController( sock );
|
|
|
|
netpacket_t * packet;
|
|
|
|
//Assert ( (sock >= 0) && (sock<net_sockets.Count()) );
|
|
|
|
// Scope for the auto_lock
|
|
{
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
|
|
// get streaming data from channel sockets
|
|
int numChannels = s_NetChannels.Count();
|
|
|
|
for ( int i = (numChannels-1); i >= 0 ; i-- )
|
|
{
|
|
CNetChan *netchan = s_NetChannels[i];
|
|
|
|
// sockets must match
|
|
if ( sock != netchan->GetSocket() )
|
|
continue;
|
|
|
|
if ( !netchan->ProcessStream() )
|
|
{
|
|
netchan->GetMsgHandler()->ConnectionCrashed("TCP connection failed.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// now get datagrams from sockets
|
|
net_scratchbuffer_t scratch;
|
|
while ( ( packet = NET_GetPacket ( sock, scratch.GetBuffer() ) ) != NULL )
|
|
{
|
|
if ( Filter_ShouldDiscard ( packet->from ) ) // filtering is done by network layer
|
|
{
|
|
Filter_SendBan( packet->from ); // tell them we aren't listening...
|
|
continue;
|
|
}
|
|
|
|
// check for connectionless packet (0xffffffff) first
|
|
if ( LittleLong( *(unsigned int *)packet->data ) == CONNECTIONLESS_HEADER )
|
|
{
|
|
packet->message.ReadLong(); // read the -1
|
|
|
|
if ( net_showudp.GetInt() && net_showudp_oob.GetInt() )
|
|
{
|
|
Msg("UDP <- %s: sz=%d OOB '0x%02X' wire=%d\n", ns_address_render( packet->from ).String(), packet->size, packet->data[4], packet->wiresize );
|
|
// for ( int k = 0; k < packet->size; ++ k )
|
|
// Msg( " %02X", packet->data[k] );
|
|
// Msg( "\n" );
|
|
// for ( int k = 0; k < packet->size; ++ k )
|
|
// Msg( " %c", (packet->data[k] >= 32 && packet->data[k] < 127) ? packet->data[k] : '*' );
|
|
// Msg( "\n" );
|
|
}
|
|
|
|
handler->ProcessConnectionlessPacket( packet );
|
|
continue;
|
|
}
|
|
|
|
// check for packets from connected clients
|
|
|
|
CNetChan * netchan = NET_FindNetChannel( sock, packet->from );
|
|
|
|
if ( netchan )
|
|
{
|
|
netchan->ProcessPacket( packet, true );
|
|
}
|
|
/* else // Not an error that may happen during connect or disconnect
|
|
{
|
|
Msg ("Sequenced packet without connection from %s\n" , ns_address_render( packet->from ).String() );
|
|
}*/
|
|
}
|
|
}
|
|
|
|
void NET_LogBadPacket(netpacket_t * packet)
|
|
{
|
|
FileHandle_t fp;
|
|
int i = 0;
|
|
char filename[ MAX_OSPATH ];
|
|
bool done = false;
|
|
|
|
while ( i < 1000 && !done )
|
|
{
|
|
Q_snprintf( filename, sizeof( filename ), "badpacket%03i.dat", i );
|
|
fp = g_pFileSystem->Open( filename, "rb" );
|
|
if ( !fp )
|
|
{
|
|
fp = g_pFileSystem->Open( filename, "wb" );
|
|
g_pFileSystem->Write( packet->data, packet->size, fp );
|
|
done = true;
|
|
}
|
|
if ( fp )
|
|
{
|
|
g_pFileSystem->Close( fp );
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if ( i < 1000 )
|
|
{
|
|
Msg( "Error buffer for %s written to %s\n", ns_address_render( packet->from ).String(), filename );
|
|
}
|
|
else
|
|
{
|
|
Msg( "Couldn't write error buffer, delete error###.dat files to make space\n" );
|
|
}
|
|
}
|
|
|
|
static int NET_SendRawPacket( SOCKET s, const void *buf, int len, const ns_address &to )
|
|
{
|
|
switch ( to.m_AddrType )
|
|
{
|
|
case NSAT_NETADR:
|
|
return g_pSteamSocketMgr->sendto( s, (const char *)buf, len, 0, to );
|
|
|
|
//case NSAT_P2P:
|
|
//{
|
|
// Assert( socket.m_pSteamNetworking );
|
|
// if ( !socket.m_pSteamNetworking )
|
|
// return -1;
|
|
// if ( socket.m_pSteamNetworking->SendP2PPacket( to.m_steamID.GetSteamID(), buf, len, k_EP2PSendUnreliable, to.m_steamID.GetSteamChannel() ) )
|
|
// return length;
|
|
//}
|
|
//break;
|
|
|
|
case NSAT_PROXIED_GAMESERVER:
|
|
{
|
|
if ( !g_pSteamDatagramClient )
|
|
{
|
|
Assert( false );
|
|
Warning( "Tried to send packet to proxied gameserver, but no ISteamDatagramTransportClient\n" );
|
|
return -1;
|
|
}
|
|
if ( to != g_addrSteamDatagramProxiedGameServer )
|
|
{
|
|
Assert( false );
|
|
Warning( "Tried to send packet to proxied gameserver %s, but client is currently pointed at gameserver %s\n", ns_address_render( to ).String(), ns_address_render( g_addrSteamDatagramProxiedGameServer ).String() );
|
|
return -1;
|
|
}
|
|
|
|
EResult result = g_pSteamDatagramClient->SendDatagram( buf, len, to.m_steamID.GetSteamChannel() );
|
|
if ( result == k_EResultOK || result == k_EResultNoConnection )
|
|
return len;
|
|
}
|
|
break;
|
|
|
|
case NSAT_PROXIED_CLIENT:
|
|
{
|
|
if ( !g_pSteamDatagramGameserver )
|
|
{
|
|
Assert( false );
|
|
Warning( "Tried to send packet to proxied client, but no ISteamDatagramTransportGameserver\n" );
|
|
return -1;
|
|
}
|
|
|
|
EResult result = g_pSteamDatagramGameserver->SendDatagram( buf, len, to.m_steamID.GetSteamID(), to.m_steamID.GetSteamChannel() );
|
|
if ( result == k_EResultOK )
|
|
return len;
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
Warning( "Attempt to send to unknown address type %d\n", to.m_AddrType );
|
|
Assert( false );
|
|
return -1;
|
|
}
|
|
|
|
int NET_SendToImpl( SOCKET s, const char * buf, int len, const ns_address &to, int iGameDataLength )
|
|
{
|
|
int nSend = 0;
|
|
if ( IsX360() || net_dedicatedForXbox )
|
|
{
|
|
// 360 uses VDP protocol to piggyback voice data across the network.
|
|
// [cbGameData][GameData][VoiceData]
|
|
// cbGameData is a two-byte prefix that contains the number of game data bytes in native order.
|
|
// XLSP servers (the only cross-platform communication possible with a secure network)
|
|
// swaps the header at the SG, decrypts the GameData and then forwards the packet to the title server.
|
|
Assert( len < (unsigned short)-1 );
|
|
const unsigned short nDataBytes = iGameDataLength == -1 ? len : iGameDataLength;
|
|
|
|
if ( voice_xsend_debug.GetBool() && iGameDataLength >= 0 && iGameDataLength != len )
|
|
{
|
|
DevMsg( "XVoice: VDP packet to %d with unencrypted %d bytes out of %d bytes\n", s, len - iGameDataLength, len );
|
|
}
|
|
|
|
const int nVDPHeaderBytes = 2;
|
|
|
|
if ( !to.IsType<netadr_t>() )
|
|
{
|
|
Warning( "NET_SendToImpl - cannot send to non-IP address %s\n", ns_address_render( to ).String() );
|
|
return -1;
|
|
}
|
|
|
|
#if defined( _WIN32 )
|
|
|
|
sockaddr sadrto;
|
|
to.AsType<netadr_t>().ToSockadr( &sadrto );
|
|
|
|
WSABUF buffers[2];
|
|
buffers[0].len = nVDPHeaderBytes;
|
|
buffers[0].buf = (char*)&nDataBytes;
|
|
buffers[1].len = len;
|
|
buffers[1].buf = const_cast<char*>( buf );
|
|
|
|
if ( nDataBytes < len && voice_verbose.GetBool() )
|
|
{
|
|
Msg( "* NET_SendToImpl: sending voice to %s (%d bytes)\n", ns_address_render( to ).String(), len - nDataBytes );
|
|
}
|
|
|
|
WSASendTo( s, buffers, 2, (DWORD*)&nSend, 0, &sadrto, sizeof(sadrto), NULL, NULL );
|
|
#else
|
|
//!!perf!! use linux sendmsg for gather similar to WSASendTo http://linux.die.net/man/3/sendmsg
|
|
uint8 *pData = ( uint8 * ) stackalloc( nVDPHeaderBytes + len );
|
|
memcpy( pData, &nDataBytes, nVDPHeaderBytes );
|
|
memcpy( pData + nVDPHeaderBytes, buf, len );
|
|
nSend = NET_SendRawPacket( s, ( const char * ) pData, nVDPHeaderBytes + len, to );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
nSend = NET_SendRawPacket( s, buf, len, to );
|
|
}
|
|
|
|
return nSend;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : sock -
|
|
// s -
|
|
// buf -
|
|
// len -
|
|
// flags -
|
|
// to -
|
|
// tolen -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
bool CL_IsHL2Demo();
|
|
bool CL_IsPortalDemo();
|
|
static int NET_SendTo( bool verbose, SOCKET s, const char * buf, int len, const ns_address &to, int iGameDataLength )
|
|
{
|
|
int nSend = 0;
|
|
|
|
|
|
// If it's 0.0.0.0:0, then it's a fake player + sv_stressbots and we've plumbed everything all
|
|
// the way through here, where we finally bail out.
|
|
if ( to.IsNull() )
|
|
{
|
|
return len;
|
|
}
|
|
|
|
// Don't send anything out in VCR mode.. it just annoys other people testing in multiplayer.
|
|
#ifndef DEDICATED
|
|
if ( ( CL_IsHL2Demo() || CL_IsPortalDemo() ) && !net_dedicated )
|
|
{
|
|
Error( "NET_SendTo: Error" );
|
|
}
|
|
#endif // _WIN32
|
|
|
|
nSend = NET_SendToImpl
|
|
(
|
|
s,
|
|
buf,
|
|
len,
|
|
to,
|
|
iGameDataLength
|
|
);
|
|
|
|
#if defined( _DEBUG )
|
|
if ( verbose &&
|
|
( nSend > 0 ) &&
|
|
( len > MAX_ROUTABLE_PAYLOAD ) )
|
|
{
|
|
ConDMsg( "NET_SendTo: Packet length (%i) > (%i) bytes\n", len, MAX_ROUTABLE_PAYLOAD );
|
|
}
|
|
#endif
|
|
return nSend;
|
|
}
|
|
|
|
#if defined( _DEBUG )
|
|
|
|
#include "filesystem.h"
|
|
#include "filesystem_engine.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : char const
|
|
//-----------------------------------------------------------------------------
|
|
char const *NET_GetDebugFilename( char const *prefix )
|
|
{
|
|
static char filename[ MAX_OSPATH ];
|
|
|
|
int i;
|
|
|
|
for ( i = 0; i < 10000; i++ )
|
|
{
|
|
Q_snprintf( filename, sizeof( filename ), "debug/%s%04i.dat", prefix, i );
|
|
if ( g_pFileSystem->FileExists( filename ) )
|
|
continue;
|
|
|
|
return filename;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *filename -
|
|
// *buf -
|
|
// len -
|
|
//-----------------------------------------------------------------------------
|
|
void NET_StorePacket( char const *filename, byte const *buf, int len )
|
|
{
|
|
FileHandle_t fh;
|
|
|
|
g_pFileSystem->CreateDirHierarchy( "debug/", "DEFAULT_WRITE_PATH" );
|
|
fh = g_pFileSystem->Open( filename, "wb" );
|
|
if ( FILESYSTEM_INVALID_HANDLE != fh )
|
|
{
|
|
g_pFileSystem->Write( buf, len, fh );
|
|
g_pFileSystem->Close( fh );
|
|
}
|
|
}
|
|
|
|
#endif // _DEBUG
|
|
|
|
struct SendQueueItem_t
|
|
{
|
|
SendQueueItem_t() :
|
|
m_pChannel( NULL ),
|
|
m_Socket( (SOCKET)-1 )
|
|
{
|
|
}
|
|
|
|
CNetChan *m_pChannel;
|
|
SOCKET m_Socket;
|
|
CUtlBuffer m_Buffer;
|
|
ns_address m_To;
|
|
};
|
|
|
|
struct SendQueue_t
|
|
{
|
|
SendQueue_t() :
|
|
m_nHostFrame( 0 )
|
|
{
|
|
}
|
|
int m_nHostFrame;
|
|
CUtlLinkedList< SendQueueItem_t > m_SendQueue;
|
|
};
|
|
|
|
static SendQueue_t g_SendQueue;
|
|
|
|
static int NET_QueuePacketForSend( CNetChan *chan, bool verbose, SOCKET s, const char *buf, int len, const ns_address &to, uint32 msecDelay )
|
|
{
|
|
// If net_queued_packet_thread was -1 at startup, then we don't even have a thread.
|
|
if ( net_queued_packet_thread.GetInt() && g_pQueuedPackedSender->IsRunning() )
|
|
{
|
|
g_pQueuedPackedSender->QueuePacket( chan, s, buf, len, to, msecDelay );
|
|
}
|
|
else
|
|
{
|
|
Assert( chan );
|
|
// Set up data structure
|
|
SendQueueItem_t *sq = &g_SendQueue.m_SendQueue[ g_SendQueue.m_SendQueue.AddToTail() ];
|
|
sq->m_Socket = s;
|
|
sq->m_pChannel = chan;
|
|
sq->m_Buffer.Put( (const void *)buf, len );
|
|
sq->m_To = to;
|
|
sq->m_pChannel->IncrementQueuedPackets();
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
void NET_SendQueuedPacket( SendQueueItem_t *sq )
|
|
{
|
|
// Msg( "Send queued packet %d\n", sq->m_Buffer.TellPut() );
|
|
NET_SendTo
|
|
(
|
|
false,
|
|
sq->m_Socket,
|
|
( const char FAR * )sq->m_Buffer.Base(),
|
|
sq->m_Buffer.TellPut(),
|
|
sq->m_To,
|
|
-1
|
|
);
|
|
|
|
sq->m_pChannel->DecrementQueuedPackets();
|
|
}
|
|
|
|
void NET_ClearQueuedPacketsForChannel( INetChannel *channel )
|
|
{
|
|
CUtlLinkedList< SendQueueItem_t >& list = g_SendQueue.m_SendQueue;
|
|
|
|
for ( unsigned short i = list.Head(); i != list.InvalidIndex(); )
|
|
{
|
|
unsigned short n = list.Next( i );
|
|
SendQueueItem_t &e = list[ i ];
|
|
if ( e.m_pChannel == channel )
|
|
{
|
|
list.Remove( i );
|
|
}
|
|
i = n;
|
|
}
|
|
}
|
|
|
|
void NET_SendQueuedPackets()
|
|
{
|
|
// Only do this once per frame
|
|
if ( host_framecount == g_SendQueue.m_nHostFrame )
|
|
return;
|
|
g_SendQueue.m_nHostFrame = host_framecount;
|
|
|
|
CUtlLinkedList< SendQueueItem_t >& list = g_SendQueue.m_SendQueue;
|
|
|
|
int nRemaining = net_splitrate.GetInt();
|
|
while ( nRemaining )
|
|
{
|
|
if ( list.IsValidIndex( list.Head() ) )
|
|
{
|
|
SendQueueItem_t *sq = &list[ list.Head() ];
|
|
NET_SendQueuedPacket( sq );
|
|
list.Remove( list.Head() );
|
|
--nRemaining;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : sock -
|
|
// s -
|
|
// buf -
|
|
// len -
|
|
// flags -
|
|
// to -
|
|
// tolen -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
static volatile int32 s_SplitPacketSequenceNumber[ MAX_SOCKETS ] = {1};
|
|
|
|
static int NET_SendLong( INetChannel *chan, int sock, SOCKET s, const char * buf, int len, const ns_address &to, int nMaxRoutableSize )
|
|
{
|
|
CNetChan *netchan = dynamic_cast< CNetChan * >( chan );
|
|
|
|
short nSplitSizeMinusHeader = nMaxRoutableSize - sizeof( SPLITPACKET );
|
|
|
|
int nSequenceNumber = -1;
|
|
if ( netchan )
|
|
{
|
|
nSequenceNumber = netchan->IncrementSplitPacketSequence();
|
|
}
|
|
else
|
|
{
|
|
nSequenceNumber = ThreadInterlockedIncrement( &s_SplitPacketSequenceNumber[ sock ] );
|
|
}
|
|
|
|
const char *sendbuf = buf;
|
|
int sendlen = len;
|
|
|
|
char packet[ MAX_ROUTABLE_PAYLOAD ];
|
|
SPLITPACKET *pPacket = (SPLITPACKET *)packet;
|
|
|
|
// Make pPacket data network endian correct
|
|
pPacket->netID = LittleLong( NET_HEADER_FLAG_SPLITPACKET );
|
|
pPacket->sequenceNumber = LittleLong( nSequenceNumber );
|
|
pPacket->nSplitSize = LittleShort( nSplitSizeMinusHeader );
|
|
|
|
int nPacketCount = (sendlen + nSplitSizeMinusHeader - 1) / nSplitSizeMinusHeader;
|
|
|
|
#if defined( _DEBUG )
|
|
if ( net_savelargesplits.GetInt() != -1 && nPacketCount >= net_savelargesplits.GetInt() )
|
|
{
|
|
char const *filename = NET_GetDebugFilename( "splitpacket" );
|
|
if ( filename )
|
|
{
|
|
Msg( "Saving split packet of %i bytes and %i packets to file %s\n",
|
|
sendlen, nPacketCount, filename );
|
|
|
|
NET_StorePacket( filename, (byte const *)sendbuf, sendlen );
|
|
}
|
|
else
|
|
{
|
|
Msg( "Too many files in debug directory, clear out old data!\n" );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int nBytesLeft = sendlen;
|
|
int nPacketNumber = 0;
|
|
int nTotalBytesSent = 0;
|
|
int nFragmentsSent = 0;
|
|
|
|
while ( nBytesLeft > 0 )
|
|
{
|
|
int size = MIN( nSplitSizeMinusHeader, nBytesLeft );
|
|
|
|
pPacket->packetID = LittleShort( (short)(( nPacketNumber << 8 ) + nPacketCount) );
|
|
|
|
Q_memcpy( packet + sizeof(SPLITPACKET), sendbuf + (nPacketNumber * nSplitSizeMinusHeader), size );
|
|
|
|
int ret = 0;
|
|
|
|
// Setting net_queued_packet_thread to NET_QUEUED_PACKET_THREAD_DEBUG_VALUE goes into a mode where all packets are queued.. can be used to stress-test it.
|
|
// Linux threads aren't prioritized well enough for this to work well (i.e. the queued packet thread doesn't get enough
|
|
// attention to flush itself well). The behavior the queue fixes is that if you send too many DP packets
|
|
// without giving up your timeslice, it'll just discard the 7th and later packets until you Sleep() (issue might be on client recipient side, need to
|
|
// snif packets to double check)
|
|
|
|
if ( netchan && (nFragmentsSent >= net_splitrate.GetInt() || net_queued_packet_thread.GetInt() == NET_QUEUED_PACKET_THREAD_DEBUG_VALUE) )
|
|
{
|
|
// Don't let this rate get too high or user's won't be able to receive all of the parts
|
|
// since they'll be too close together.
|
|
// This should use the same rate as is used elsewhere so that we throttle all sends to the
|
|
// same delays. Note that setting the socket's send/receive buffers to a larger size helps
|
|
// to minimize the issues. However unthrottled UDP is still a bad thing.
|
|
|
|
float flMaxSplitpacketDataRateBytesPerSecond = (float)netchan->GetDataRate();
|
|
|
|
// Calculate the delay (measured from now) for when this packet should be sent.
|
|
uint32 delay = (int)( 1000.0f * ( (float)( nPacketNumber * ( nMaxRoutableSize + UDP_HEADER_SIZE ) ) / flMaxSplitpacketDataRateBytesPerSecond ) + 0.5f );
|
|
|
|
ret = NET_QueuePacketForSend( netchan, false, s, packet, size + sizeof(SPLITPACKET), to, delay );
|
|
}
|
|
else
|
|
{
|
|
// Also, we send the first packet no matter what
|
|
// w/o a netchan, if there are too many splits, its possible the packet can't be delivered. However, this would only apply to out of band stuff like
|
|
// server query packets, which should never require splitting anyway.
|
|
ret = NET_SendTo( false, s, packet, size + sizeof(SPLITPACKET), to, -1 );
|
|
}
|
|
|
|
// First split send
|
|
++nFragmentsSent;
|
|
|
|
if ( ret < 0 )
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if ( ret >= size )
|
|
{
|
|
nTotalBytesSent += size;
|
|
}
|
|
|
|
nBytesLeft -= size;
|
|
++nPacketNumber;
|
|
|
|
// Always bitch about split packets in debug
|
|
if ( net_showsplits.GetInt() && net_showsplits.GetInt() != 2 )
|
|
{
|
|
Msg( "--> [%s] Split packet %4i/%4i seq %5i size %4i mtu %4i to %s [ total %4i ]\n",
|
|
DescribeSocket( sock ),
|
|
nPacketNumber,
|
|
nPacketCount,
|
|
nSequenceNumber,
|
|
size,
|
|
nMaxRoutableSize,
|
|
ns_address_render( to ).String(),
|
|
sendlen );
|
|
}
|
|
}
|
|
|
|
return nTotalBytesSent;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : sock -
|
|
// length -
|
|
// *data -
|
|
// to -
|
|
// Output : void NET_SendPacket
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int NET_SendPacket ( INetChannel *chan, int sock, const ns_address &to, const unsigned char *data, int length, bf_write *pVoicePayload /* = NULL */, bool bUseCompression /*=false*/, uint32 unMillisecondsDelay /*=0u*/ )
|
|
{
|
|
int ret;
|
|
|
|
if ( (*(unsigned int*)data == CONNECTIONLESS_HEADER) && bUseCompression )
|
|
{
|
|
Warning( "[NET] Cannot send compressed connectionless packet to %s '0x%02X'\n", ns_address_render( to ).String(), data[4] );
|
|
Assert( 0 );
|
|
return 0;
|
|
}
|
|
if ( ( *( unsigned int* ) data == CONNECTIONLESS_HEADER ) && ( length > MAX_ROUTABLE_PAYLOAD ) )
|
|
{
|
|
Warning( "[NET] Cannot send connectionless packet to %s '0x%02X' exceeding MTU (%u)\n", ns_address_render( to ).String(), data[ 4 ], length );
|
|
Assert( 0 );
|
|
return 0;
|
|
}
|
|
|
|
if ( net_showudp.GetInt() && (*(unsigned int*)data == CONNECTIONLESS_HEADER) && net_showudp_oob.GetInt() )
|
|
{
|
|
Assert( !bUseCompression );
|
|
if ( !net_showudp_remoteonly.GetBool() || !( to.IsLocalhost() || to.IsLoopback() ) )
|
|
{
|
|
Msg("UDP -> %s: sz=%d OOB '0x%02X'\n", ns_address_render( to ).String(), length, data[4] );
|
|
}
|
|
}
|
|
|
|
if ( !NET_IsMultiplayer() || to.IsLoopback() || ( to.IsLocalhost() && !net_usesocketsforloopback.GetBool() ) )
|
|
{
|
|
Assert( !pVoicePayload );
|
|
|
|
NET_SendLoopPacket(sock, length, data );
|
|
return length;
|
|
}
|
|
|
|
int net_socket = 0;
|
|
if ( to.IsType<netadr_t>() )
|
|
{
|
|
net_socket = net_sockets[sock].hUDP;
|
|
if (!net_socket)
|
|
return length;
|
|
}
|
|
|
|
if ( (droppackets.GetInt() < 0) && sock == NS_CLIENT )
|
|
{
|
|
droppackets.SetValue( droppackets.GetInt() + 1 );
|
|
return length;
|
|
}
|
|
|
|
if ( fakeloss.GetFloat() > 0.0f )
|
|
{
|
|
// simulate sending this packet
|
|
if (RandomInt(0,100) <= (int)fakeloss.GetFloat())
|
|
return length;
|
|
}
|
|
|
|
MEM_ALLOC_CREDIT();
|
|
CUtlMemoryFixedGrowable< byte, NET_COMPRESSION_STACKBUF_SIZE > memCompressed( NET_COMPRESSION_STACKBUF_SIZE );
|
|
CUtlMemoryFixedGrowable< byte, NET_COMPRESSION_STACKBUF_SIZE > memCompressedVoice( NET_COMPRESSION_STACKBUF_SIZE );
|
|
CUtlMemoryFixedGrowable< byte, NET_COMPRESSION_STACKBUF_SIZE > memEncryptedAll( NET_COMPRESSION_STACKBUF_SIZE );
|
|
|
|
int iGameDataLength = pVoicePayload ? length : -1;
|
|
|
|
bool bWroteVoice = false;
|
|
unsigned int nVoiceBytes = 0;
|
|
|
|
if ( pVoicePayload )
|
|
{
|
|
memCompressedVoice.EnsureCapacity( pVoicePayload->GetNumBytesWritten() + sizeof( unsigned short ) );
|
|
|
|
byte *pVoice = (byte *)memCompressedVoice.Base();
|
|
|
|
unsigned short usVoiceBits = pVoicePayload->GetNumBitsWritten();
|
|
*( unsigned short * )pVoice = LittleShort( usVoiceBits );
|
|
pVoice += sizeof( unsigned short );
|
|
|
|
unsigned int nCompressedLength = pVoicePayload->GetNumBytesWritten();
|
|
byte *pOutput = NULL;
|
|
if ( net_compressvoice.GetBool() )
|
|
{
|
|
CLZSS lzss;
|
|
pOutput = lzss.CompressNoAlloc( pVoicePayload->GetData(), pVoicePayload->GetNumBytesWritten(), (byte *)pVoice, &nCompressedLength );
|
|
}
|
|
if ( !pOutput )
|
|
{
|
|
Q_memcpy( pVoice, pVoicePayload->GetData(), pVoicePayload->GetNumBytesWritten() );
|
|
}
|
|
|
|
nVoiceBytes = nCompressedLength + sizeof( unsigned short );
|
|
}
|
|
|
|
if ( voice_xsend_debug.GetBool() && nVoiceBytes )
|
|
{
|
|
DevMsg( "XVoice: voice data payload for %p: %d bytes\n", chan, nVoiceBytes );
|
|
}
|
|
|
|
if ( bUseCompression )
|
|
{
|
|
CLZSS lzss;
|
|
unsigned int nCompressedLength = length;
|
|
|
|
memCompressed.EnsureCapacity( length + nVoiceBytes + sizeof( unsigned int ) );
|
|
|
|
*(int *)memCompressed.Base() = LittleLong( NET_HEADER_FLAG_COMPRESSEDPACKET );
|
|
|
|
byte *pOutput = lzss.CompressNoAlloc( (byte *)data, length, memCompressed.Base() + sizeof( unsigned int ), &nCompressedLength );
|
|
if ( pOutput )
|
|
{
|
|
data = memCompressed.Base();
|
|
length = nCompressedLength + sizeof( unsigned int );
|
|
|
|
if ( pVoicePayload && pVoicePayload->GetNumBitsWritten() > 0 )
|
|
{
|
|
byte *pVoice = (byte *)memCompressed.Base() + length;
|
|
Q_memcpy( pVoice, memCompressedVoice.Base(), nVoiceBytes );
|
|
}
|
|
|
|
iGameDataLength = length;
|
|
|
|
length += nVoiceBytes;
|
|
|
|
bWroteVoice = true;
|
|
}
|
|
}
|
|
|
|
if ( !bWroteVoice && pVoicePayload && pVoicePayload->GetNumBitsWritten() > 0 )
|
|
{
|
|
memCompressed.EnsureCapacity( length + nVoiceBytes );
|
|
|
|
byte *pVoice = (byte *)memCompressed.Base();
|
|
Q_memcpy( pVoice, (const void *)data, length );
|
|
pVoice += length;
|
|
Q_memcpy( pVoice, memCompressedVoice.Base(), nVoiceBytes );
|
|
data = memCompressed.Base();
|
|
|
|
length += nVoiceBytes;
|
|
}
|
|
|
|
// If the network channel has encryption key then we should encrypt
|
|
if ( const unsigned char *pubEncryptionKey = chan ? chan->GetChannelEncryptionKey() : NULL )
|
|
{
|
|
IceKey iceKey( 2 );
|
|
iceKey.set( pubEncryptionKey );
|
|
|
|
// Generate some random fudge, ICE operates on 64-bit blocks, so make sure our total size is a multiple of 8 bytes
|
|
int numRandomFudgeBytes = RandomInt( 16, 72 );
|
|
int numTotalEncryptedBytes = 1 + numRandomFudgeBytes + sizeof( int32 ) + length;
|
|
numRandomFudgeBytes += iceKey.blockSize() - ( numTotalEncryptedBytes % iceKey.blockSize() );
|
|
numTotalEncryptedBytes = 1 + numRandomFudgeBytes + sizeof( int32 ) + length;
|
|
|
|
char *pchRandomFudgeBytes = ( char * ) stackalloc( numRandomFudgeBytes );
|
|
for ( int k = 0; k < numRandomFudgeBytes; ++k )
|
|
pchRandomFudgeBytes[ k ] = RandomInt( 16, 250 );
|
|
|
|
// Prepare the encrypted memory
|
|
memEncryptedAll.EnsureCapacity( numTotalEncryptedBytes );
|
|
* memEncryptedAll.Base() = numRandomFudgeBytes;
|
|
Q_memcpy( memEncryptedAll.Base() + 1, pchRandomFudgeBytes, numRandomFudgeBytes );
|
|
|
|
int32 const numBytesWrittenWire = BigLong( length ); // byteswap for the wire
|
|
Q_memcpy( memEncryptedAll.Base() + 1 + numRandomFudgeBytes, &numBytesWrittenWire, sizeof( numBytesWrittenWire ) );
|
|
Q_memcpy( memEncryptedAll.Base() + 1 + numRandomFudgeBytes + sizeof( int32 ), data, length );
|
|
|
|
// Encrypt the message
|
|
unsigned char *pchCryptoBuffer = ( unsigned char * ) stackalloc( iceKey.blockSize() );
|
|
for ( int k = 0; k < numTotalEncryptedBytes; k += iceKey.blockSize() )
|
|
{
|
|
iceKey.encrypt( ( const unsigned char * ) ( memEncryptedAll.Base() + k ), pchCryptoBuffer );
|
|
Q_memcpy( memEncryptedAll.Base() + k, pchCryptoBuffer, iceKey.blockSize() );
|
|
}
|
|
|
|
// Set the pointers to network out the encrypted data
|
|
data = memEncryptedAll.Base();
|
|
length = numTotalEncryptedBytes;
|
|
}
|
|
|
|
// Do we need to break this packet up?
|
|
int nMaxRoutable = MAX_ROUTABLE_PAYLOAD;
|
|
if ( chan )
|
|
{
|
|
nMaxRoutable = clamp( chan->GetMaxRoutablePayloadSize(), MIN_USER_MAXROUTABLE_SIZE, MIN( sv_maxroutable.GetInt(), MAX_USER_MAXROUTABLE_SIZE ) );
|
|
}
|
|
|
|
if ( ( unMillisecondsDelay != 0u ) &&
|
|
( length > nMaxRoutable ) )
|
|
{
|
|
Warning( "Can't delay send a packet larger than maxroutable size %d/%d\n", length, nMaxRoutable );
|
|
unMillisecondsDelay = 0u;
|
|
}
|
|
|
|
if ( unMillisecondsDelay != 0u )
|
|
{
|
|
ret = NET_QueuePacketForSend( static_cast< CNetChan * >( chan ), false, net_socket, (const char *)data, length, to, unMillisecondsDelay );
|
|
}
|
|
else if ( length <= nMaxRoutable &&
|
|
!(net_queued_packet_thread.GetInt() == NET_QUEUED_PACKET_THREAD_DEBUG_VALUE && chan ) )
|
|
{
|
|
// simple case, small packet, just send it
|
|
ret = NET_SendTo( true, net_socket, (const char *)data, length, to, iGameDataLength );
|
|
}
|
|
else
|
|
{
|
|
// split packet into smaller pieces
|
|
ret = NET_SendLong( chan, sock, net_socket, (const char *)data, length, to, nMaxRoutable );
|
|
}
|
|
|
|
if (ret == -1)
|
|
{
|
|
NET_GetLastError();
|
|
|
|
// wouldblock is silent
|
|
if ( net_error == WSAEWOULDBLOCK )
|
|
return 0;
|
|
|
|
if ( net_error == WSAECONNRESET )
|
|
return 0;
|
|
|
|
// some PPP links dont allow broadcasts
|
|
if ( ( net_error == WSAEADDRNOTAVAIL) && ( to.IsBroadcast() ) )
|
|
return 0;
|
|
|
|
ConDMsg ("NET_SendPacket Warning: %s : %s\n", NET_ErrorString(net_error), ns_address_render( to ).String() );
|
|
ret = length;
|
|
}
|
|
|
|
|
|
return ret;
|
|
}
|
|
|
|
void NET_OutOfBandPrintf(int sock, const ns_address &adr, const char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
char string[MAX_ROUTABLE_PAYLOAD];
|
|
|
|
*(unsigned int*)string = CONNECTIONLESS_HEADER;
|
|
|
|
va_start (argptr, format);
|
|
Q_vsnprintf (string+4, sizeof( string ) - 4, format,argptr);
|
|
va_end (argptr);
|
|
|
|
int length = Q_strlen(string+4) + 5;
|
|
|
|
NET_SendPacket ( NULL, sock, adr, (byte *)string, length );
|
|
}
|
|
|
|
void NET_OutOfBandDelayedPrintf(int sock, const ns_address &adr, uint32 unMillisecondsDelay, const char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
char string[MAX_ROUTABLE_PAYLOAD];
|
|
|
|
*(unsigned int*)string = CONNECTIONLESS_HEADER;
|
|
|
|
va_start (argptr, format);
|
|
Q_vsnprintf (string+4, sizeof( string ) - 4, format,argptr);
|
|
va_end (argptr);
|
|
|
|
int length = Q_strlen(string+4) + 5;
|
|
|
|
NET_SendPacket ( NULL, sock, adr, (byte *)string, length, 0, false, unMillisecondsDelay );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_CloseAllSockets
|
|
====================
|
|
*/
|
|
void NET_CloseAllSockets (void)
|
|
{
|
|
// shut down any existing and open sockets
|
|
for (int i=0 ; i<net_sockets.Count() ; i++)
|
|
{
|
|
if ( net_sockets[i].nPort )
|
|
{
|
|
NET_CloseSocket( net_sockets[i].hUDP );
|
|
NET_CloseSocket( net_sockets[i].hTCP );
|
|
|
|
net_sockets[i].nPort = 0;
|
|
net_sockets[i].bListening = false;
|
|
net_sockets[i].hUDP = 0;
|
|
net_sockets[i].hTCP = 0;
|
|
}
|
|
}
|
|
|
|
// shut down all pending sockets
|
|
AUTO_LOCK_FM( s_PendingSockets );
|
|
for(int j=0; j<s_PendingSockets.Count();j++ )
|
|
{
|
|
NET_CloseSocket( s_PendingSockets[j].newsock );
|
|
}
|
|
|
|
s_PendingSockets.RemoveAll();
|
|
|
|
// Close steam sockets as well
|
|
g_pSteamSocketMgr->Shutdown();
|
|
g_pSteamSocketMgr->Init();
|
|
|
|
// Shutdown steam datagram server, if we were listening
|
|
if ( g_pSteamDatagramGameserver )
|
|
{
|
|
g_pSteamDatagramGameserver->Destroy();
|
|
g_pSteamDatagramGameserver = NULL;
|
|
}
|
|
|
|
// Shutdown steam datagram client, if we have one
|
|
CloseSteamDatagramClientConnection();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_FlushAllSockets
|
|
====================
|
|
*/
|
|
void NET_FlushAllSockets( void )
|
|
{
|
|
// drain any packets that my still lurk in our incoming queue
|
|
char data[2048];
|
|
ns_address from;
|
|
|
|
for (int i=0 ; i<net_sockets.Count() ; i++)
|
|
{
|
|
if ( net_sockets[i].hUDP )
|
|
{
|
|
int bytes = 1;
|
|
|
|
// loop until no packets are pending anymore
|
|
while ( bytes > 0 )
|
|
{
|
|
bytes = NET_ReceiveRawPacket( net_sockets[i].hUDP, data, sizeof(data), &from );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined( IS_WINDOWS_PC )
|
|
#include <Iphlpapi.h>
|
|
|
|
// Simple helper class to enumerate and cache of IP addresses of local network adapters
|
|
class CBindAddressHelper
|
|
{
|
|
public:
|
|
CBindAddressHelper() : m_bInitialized( false )
|
|
{
|
|
}
|
|
|
|
void GetBindAddresses( CUtlVector< CUtlString >& list )
|
|
{
|
|
if ( !m_bInitialized )
|
|
{
|
|
m_bInitialized = true;
|
|
BuildBindAddresses( m_CachedAddresses );
|
|
}
|
|
|
|
for ( int i = 0; i < m_CachedAddresses.Count(); ++i )
|
|
{
|
|
list.AddToTail( m_CachedAddresses[ i ] );
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
void BuildBindAddresses( CUtlVector< CUtlString >& list )
|
|
{
|
|
IP_ADAPTER_INFO info_temp;
|
|
ULONG len = 0;
|
|
if ( GetAdaptersInfo( &info_temp, &len ) != ERROR_BUFFER_OVERFLOW )
|
|
return;
|
|
IP_ADAPTER_INFO *infos = new IP_ADAPTER_INFO[ len ];
|
|
if ( !infos )
|
|
{
|
|
Sys_Error( "BuildBindAddresses: Out of memory allocating %d bytes\n", sizeof( IP_ADAPTER_INFO ) * len );
|
|
return;
|
|
}
|
|
|
|
if ( GetAdaptersInfo( infos, &len ) == NO_ERROR )
|
|
{
|
|
for ( IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next )
|
|
{
|
|
if ( info->Type == MIB_IF_TYPE_LOOPBACK )
|
|
continue;
|
|
if ( !Q_strcmp( info->IpAddressList.IpAddress.String, "0.0.0.0" ) )
|
|
continue;
|
|
|
|
DevMsg( "NET_GetBindAddresses found %s: '%s'\n", info->IpAddressList.IpAddress.String, info->Description );
|
|
list.AddToTail( CUtlString( info->IpAddressList.IpAddress.String ) );
|
|
}
|
|
}
|
|
delete[] infos;
|
|
}
|
|
|
|
bool m_bInitialized;
|
|
CUtlVector< CUtlString > m_CachedAddresses;
|
|
};
|
|
static CBindAddressHelper g_BindAddressHelper;
|
|
#endif
|
|
|
|
#ifndef DEDICATED
|
|
|
|
// Initialize steam client datagram lib if we haven't already
|
|
static bool CheckInitSteamDatagramClientLib()
|
|
{
|
|
static bool bInittedNetwork = false;
|
|
if ( bInittedNetwork )
|
|
return true;
|
|
|
|
if ( !Steam3Client().SteamHTTP() )
|
|
{
|
|
Warning( "Cannot init steam datagram client, no Steam HTTP interface\n" );
|
|
return false;
|
|
}
|
|
|
|
// Locate the first PLATFORM path
|
|
char szAbsPlatform[MAX_FILEPATH] = "";
|
|
const char *pszConfigDir = "config";
|
|
g_pFullFileSystem->GetSearchPath( "PLATFORM", false, szAbsPlatform, sizeof(szAbsPlatform) );
|
|
|
|
char *semi = strchr( szAbsPlatform, ';' );
|
|
if ( semi )
|
|
*semi = '\0';
|
|
|
|
// Set partner. Running in china?
|
|
ESteamDatagramPartner ePartner = k_ESteamDatagramPartner_Steam;
|
|
if ( CommandLine()->HasParm( "-perfectworld" ) )
|
|
ePartner = k_ESteamDatagramPartner_China;
|
|
int iPartnerMark = -1; // CSGO doesn't prune the config based on partner!
|
|
|
|
char szAbsConfigDir[ MAX_FILEPATH];
|
|
V_ComposeFileName( szAbsPlatform, pszConfigDir, szAbsConfigDir, sizeof(szAbsConfigDir) );
|
|
SteamDatagramClient_Init( szAbsConfigDir, ePartner, iPartnerMark );
|
|
bInittedNetwork = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void NET_PrintSteamdatagramClientStatus()
|
|
{
|
|
if ( !g_pSteamDatagramClient )
|
|
{
|
|
Msg( "No steam datagram client connection active\n" );
|
|
return;
|
|
}
|
|
ISteamDatagramTransportClient::ConnectionStatus status;
|
|
g_pSteamDatagramClient->GetConnectionStatus( status );
|
|
int sz = status.Print( NULL, 0 );
|
|
CUtlMemory<char> buf;
|
|
buf.EnsureCapacity( sz );
|
|
char *p = buf.Base();
|
|
status.Print( p, sz );
|
|
for (;;)
|
|
{
|
|
char *newline = strchr( p, '\n' );
|
|
if ( newline )
|
|
*newline = '\0';
|
|
Msg( "%s\n", p );
|
|
if ( !newline )
|
|
break;
|
|
p = newline+1;
|
|
}
|
|
}
|
|
CON_COMMAND( steamdatagram_client_status, "Print steam datagram client status" )
|
|
{
|
|
NET_PrintSteamdatagramClientStatus();
|
|
}
|
|
|
|
bool NET_InitSteamDatagramProxiedGameserverConnection( const ns_address &adr )
|
|
{
|
|
Assert( adr.GetAddressType() == NSAT_PROXIED_GAMESERVER );
|
|
|
|
// Most common case - talking to the same server as before
|
|
if ( g_pSteamDatagramClient )
|
|
{
|
|
if ( g_addrSteamDatagramProxiedGameServer.m_steamID.GetSteamID() == adr.m_steamID.GetSteamID() )
|
|
{
|
|
g_addrSteamDatagramProxiedGameServer.m_steamID.SetSteamChannel( adr.m_steamID.GetSteamChannel() );
|
|
return true;
|
|
}
|
|
|
|
// We have a client, but it was to talk to a different server. Clear our ticket!
|
|
g_pSteamDatagramClient->Close();
|
|
g_addrSteamDatagramProxiedGameServer.Clear();
|
|
}
|
|
|
|
// Get a client to talk to this server
|
|
g_pSteamDatagramClient = SteamDatagramClient_Connect( adr.m_steamID.GetSteamID() );
|
|
if ( !g_pSteamDatagramClient )
|
|
return false;
|
|
|
|
// OK, remember who we're talking to
|
|
g_addrSteamDatagramProxiedGameServer = adr;
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
static void OpenSocketInternal( int nModule, int nSetPort, int nDefaultPort, const char *pName, int nProtocol, bool bTryAny )
|
|
{
|
|
CUtlVector< CUtlString > vecBindableAddresses;
|
|
if ( ( NS_HLTV == nModule )&& ipname_tv.GetString()[0] )
|
|
{
|
|
vecBindableAddresses.AddToTail( CUtlString( ipname_tv.GetString() ) );
|
|
}
|
|
else if ( ( NS_HLTV1 == nModule ) && ipname_tv1.GetString()[ 0 ] )
|
|
{
|
|
vecBindableAddresses.AddToTail( CUtlString( ipname_tv1.GetString() ) );
|
|
}
|
|
else
|
|
{
|
|
vecBindableAddresses.AddToTail( CUtlString( ipname.GetString() ) );
|
|
}
|
|
#if defined( IS_WINDOWS_PC )
|
|
g_BindAddressHelper.GetBindAddresses( vecBindableAddresses );
|
|
#endif
|
|
|
|
int port = nSetPort ? nSetPort : nDefaultPort;
|
|
int *handle = NULL;
|
|
if( nProtocol == IPPROTO_TCP )
|
|
{
|
|
handle = &net_sockets[nModule].hTCP;
|
|
}
|
|
else if ( nProtocol == IPPROTO_UDP || nProtocol == IPPROTO_VDP )
|
|
{
|
|
handle = &net_sockets[nModule].hUDP;
|
|
}
|
|
else
|
|
{
|
|
Sys_Error( "Unrecognized protocol type %d", nProtocol );
|
|
return;
|
|
}
|
|
|
|
if ( !net_sockets[nModule].nPort )
|
|
{
|
|
int nSavePort = port;
|
|
for ( int i = 0; i < vecBindableAddresses.Count(); ++i )
|
|
{
|
|
port = nSavePort;
|
|
|
|
char const *pchIPAddressToBind = vecBindableAddresses[ i ].String();
|
|
|
|
if ( i > 0 )
|
|
{
|
|
Msg( "Trying to open socket on %s\n", pchIPAddressToBind );
|
|
}
|
|
|
|
// If we are only using Steam sockets, get a fake handle
|
|
*handle = OnlyUseSteamSockets() ? ++g_nFakeSocketHandle : NET_OpenSocket (pchIPAddressToBind, port, nProtocol );
|
|
|
|
if ( !OnlyUseSteamSockets() && !*handle && bTryAny )
|
|
{
|
|
port = PORT_ANY; // try again with PORT_ANY
|
|
*handle = NET_OpenSocket (pchIPAddressToBind, port, nProtocol );
|
|
}
|
|
|
|
// Stop on first success
|
|
if ( *handle )
|
|
break;
|
|
}
|
|
|
|
if ( !*handle )
|
|
{
|
|
Warning( "Couldn't allocate any %s IP port, tried %d addresses %s", pName, vecBindableAddresses.Count(),
|
|
( vecBindableAddresses.Count() ? vecBindableAddresses.Head().Get() : "(none)" ) );
|
|
Plat_ExitProcess( 0 ); // cause a silent exit without writing a core dump
|
|
return;
|
|
}
|
|
|
|
net_sockets[nModule].nPort = port;
|
|
}
|
|
else
|
|
{
|
|
Msg("WARNING: NET_OpenSockets: %s port %i already open.\n", pName, net_sockets[nModule].nPort );
|
|
}
|
|
|
|
if ( handle )
|
|
{
|
|
g_pSteamSocketMgr->OpenSocket( *handle, nModule, nSetPort, nDefaultPort, pName, nProtocol, bTryAny );
|
|
}
|
|
#ifndef DEDICATED
|
|
if ( nModule == NS_CLIENT )
|
|
CheckInitSteamDatagramClientLib();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_OpenSockets
|
|
====================
|
|
*/
|
|
void NET_OpenSockets (void)
|
|
{
|
|
// Xbox 360 uses VDP protocol to combine encrypted game data with clear voice data
|
|
const int nProtocol = IsX360() ? IPPROTO_VDP : IPPROTO_UDP;
|
|
|
|
OpenSocketInternal( NS_SERVER, hostport.GetInt(), PORT_SERVER, "server", nProtocol, false );
|
|
OpenSocketInternal( NS_CLIENT, clientport.GetInt(), PORT_SERVER, "client", nProtocol, true );
|
|
|
|
#ifdef _X360
|
|
int nX360Port = PORT_X360_RESERVED_FIRST;
|
|
OpenSocketInternal( NS_X360_SYSTEMLINK, 0, nX360Port ++, "x360systemlink", IPPROTO_UDP, false );
|
|
OpenSocketInternal( NS_X360_LOBBY, 0, nX360Port ++, "x360lobby", nProtocol, false );
|
|
OpenSocketInternal( NS_X360_TEAMLINK, 0, nX360Port ++, "x360teamlink", nProtocol, false );
|
|
Assert( nX360Port <= PORT_X360_RESERVED_LAST );
|
|
#endif
|
|
|
|
if ( !net_nohltv )
|
|
{
|
|
OpenSocketInternal( NS_HLTV, hltvport.GetInt(), PORT_HLTV, "hltv", nProtocol, false );
|
|
if ( net_addhltv1 )
|
|
{
|
|
OpenSocketInternal( NS_HLTV1, hltvport1.GetInt(), PORT_HLTV1, "hltv1", nProtocol, false );
|
|
}
|
|
}
|
|
#if defined( REPLAY_ENABLED )
|
|
if ( !net_noreplay )
|
|
{
|
|
OpenSocketInternal( NS_REPLAY, replayport.GetInt(), PORT_REPLAY, "replay", nProtocol, false );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool NET_IsSocketOpen( int nSockIdx )
|
|
{
|
|
if ( !net_sockets.IsValidIndex( nSockIdx ) )
|
|
return false;
|
|
|
|
if ( !net_sockets[nSockIdx].hUDP )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
int NET_AddExtraSocket( int port )
|
|
{
|
|
int newSocket = net_sockets.AddToTail();
|
|
|
|
Q_memset( &net_sockets[newSocket], 0, sizeof(netsocket_t) );
|
|
|
|
OpenSocketInternal( newSocket, port, PORT_ANY, "extra", IPPROTO_UDP, true );
|
|
|
|
net_packets.EnsureCount( newSocket+1 );
|
|
net_splitpackets.EnsureCount( newSocket+1 );
|
|
|
|
return newSocket;
|
|
}
|
|
|
|
void NET_RemoveAllExtraSockets()
|
|
{
|
|
for (int i=MAX_SOCKETS ; i<net_sockets.Count() ; i++)
|
|
{
|
|
if ( net_sockets[i].nPort )
|
|
{
|
|
NET_CloseSocket( net_sockets[i].hUDP );
|
|
NET_CloseSocket( net_sockets[i].hTCP );
|
|
}
|
|
}
|
|
net_sockets.RemoveMultiple( MAX_SOCKETS, net_sockets.Count()-MAX_SOCKETS );
|
|
|
|
Assert( net_sockets.Count() == MAX_SOCKETS );
|
|
}
|
|
|
|
unsigned short NET_GetUDPPort(int socket)
|
|
{
|
|
return net_sockets.IsValidIndex( socket ) ?
|
|
net_sockets[socket].nPort : 0;
|
|
}
|
|
|
|
static void NET_ConPrintByteStream( uint8 const *pb, int nSize )
|
|
{
|
|
for ( int k = 0; k < nSize; ++ k, ++ pb )
|
|
Msg( " %02X", *pb );
|
|
}
|
|
|
|
/*
|
|
================
|
|
NET_GetLocalAddress
|
|
|
|
Returns the servers' ip address as a string.
|
|
================
|
|
*/
|
|
void NET_GetLocalAddress (void)
|
|
{
|
|
net_local_adr.Clear();
|
|
|
|
if ( net_noip )
|
|
{
|
|
Msg("TCP/UDP Disabled.\n");
|
|
}
|
|
else
|
|
{
|
|
#ifdef _X360
|
|
int err = 0;
|
|
XNADDR xnaddrLocal;
|
|
ZeroMemory( &xnaddrLocal, sizeof( xnaddrLocal ) );
|
|
while( XNET_GET_XNADDR_PENDING == ( err = XNetGetTitleXnAddr( &xnaddrLocal ) ) )
|
|
continue;
|
|
|
|
static struct XnAddrType_t
|
|
{
|
|
int m_code;
|
|
char const *m_szValue;
|
|
}
|
|
arrXnAddrTypes[] = {
|
|
{ XNET_GET_XNADDR_NONE, "NONE" },
|
|
{ XNET_GET_XNADDR_ETHERNET, "ETHERNET" },
|
|
{ XNET_GET_XNADDR_STATIC, "STATIC" },
|
|
{ XNET_GET_XNADDR_DHCP, "DHCP" },
|
|
{ XNET_GET_XNADDR_PPPOE, "PPPoE" },
|
|
{ XNET_GET_XNADDR_GATEWAY, "GATEWAY" },
|
|
{ XNET_GET_XNADDR_DNS, "DNS" },
|
|
{ XNET_GET_XNADDR_ONLINE, "ONLINE" },
|
|
{ XNET_GET_XNADDR_TROUBLESHOOT, "TROUBLESHOOT" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
Msg( "Local XNetwork address type 0x%08X", err );
|
|
for ( XnAddrType_t const *pxat = arrXnAddrTypes; pxat->m_code; ++ pxat )
|
|
{
|
|
if ( ( err & pxat->m_code ) == pxat->m_code )
|
|
Msg( " %s", pxat->m_szValue );
|
|
}
|
|
Msg( "\n" );
|
|
|
|
net_local_adr.SetFromString( "127.0.0.1" );
|
|
Msg( "Local IP address: %d.%d.%d.%d\n",
|
|
xnaddrLocal.ina.S_un.S_un_b.s_b1,
|
|
xnaddrLocal.ina.S_un.S_un_b.s_b2,
|
|
xnaddrLocal.ina.S_un.S_un_b.s_b3,
|
|
xnaddrLocal.ina.S_un.S_un_b.s_b4 );
|
|
|
|
#elif defined( _PS3 )
|
|
CellNetCtlInfo cnci;
|
|
memset( &cnci, 0, sizeof( cnci ) );
|
|
|
|
// Print CELL network information for debug output
|
|
Msg( "=========== CELL network information ===========\n" );
|
|
for ( int iCellInfo = CELL_NET_CTL_INFO_DEVICE; iCellInfo <= CELL_NET_CTL_INFO_UPNP_CONFIG; ++ iCellInfo )
|
|
{
|
|
int ret = cellNetCtlGetInfo( iCellInfo, &cnci );
|
|
if ( CELL_OK != ret )
|
|
{
|
|
Warning( "NET: failed to obtain CELL NET INFO #%d, error code %d.\n", iCellInfo, ret );
|
|
}
|
|
else switch ( iCellInfo )
|
|
{
|
|
case CELL_NET_CTL_INFO_DEVICE:
|
|
Msg( " Device: %u\n", cnci.device );
|
|
break;
|
|
case CELL_NET_CTL_INFO_ETHER_ADDR:
|
|
Msg( " Ethernet Address: [" );
|
|
NET_ConPrintByteStream( cnci.ether_addr.data, sizeof( cnci.ether_addr.data ) );
|
|
NET_ConPrintByteStream( cnci.ether_addr.padding, sizeof( cnci.ether_addr.padding ) );
|
|
Msg( " ]\n" );
|
|
break;
|
|
case CELL_NET_CTL_INFO_MTU:
|
|
Msg( " MTU: %u\n", cnci.mtu );
|
|
break;
|
|
case CELL_NET_CTL_INFO_LINK:
|
|
Msg( " Link: %u\n", cnci.link );
|
|
break;
|
|
case CELL_NET_CTL_INFO_LINK_TYPE:
|
|
Msg( " Link type: %u\n", cnci.link_type );
|
|
break;
|
|
case CELL_NET_CTL_INFO_BSSID:
|
|
Msg( " BSSID Address: [" );
|
|
NET_ConPrintByteStream( cnci.bssid.data, sizeof( cnci.bssid.data ) );
|
|
NET_ConPrintByteStream( cnci.bssid.padding, sizeof( cnci.bssid.padding ) );
|
|
Msg( " ]\n" );
|
|
break;
|
|
case CELL_NET_CTL_INFO_SSID:
|
|
Msg( " SSID Address: [" );
|
|
NET_ConPrintByteStream( cnci.ssid.data, sizeof( cnci.ssid.data ) );
|
|
NET_ConPrintByteStream( &cnci.ssid.term, sizeof( cnci.ssid.term ) );
|
|
NET_ConPrintByteStream( cnci.ssid.padding, sizeof( cnci.ssid.padding ) );
|
|
Msg( " ]\n" );
|
|
break;
|
|
case CELL_NET_CTL_INFO_WLAN_SECURITY:
|
|
Msg( " WLAN security: %u\n", cnci.wlan_security );
|
|
break;
|
|
case CELL_NET_CTL_INFO_8021X_TYPE:
|
|
Msg( " WAuth 8021x type: %u\n", cnci.auth_8021x_type );
|
|
break;
|
|
case CELL_NET_CTL_INFO_8021X_AUTH_NAME:
|
|
Msg( " WAuth 8021x name: %s\n", cnci.auth_8021x_auth_name );
|
|
break;
|
|
case CELL_NET_CTL_INFO_RSSI:
|
|
Msg( " WRSSI: %u\n", cnci.rssi );
|
|
break;
|
|
case CELL_NET_CTL_INFO_CHANNEL:
|
|
Msg( " WChannel: %u\n", cnci.channel );
|
|
break;
|
|
case CELL_NET_CTL_INFO_IP_CONFIG:
|
|
Msg( " Ipconfig: %u\n", cnci.ip_config );
|
|
break;
|
|
case CELL_NET_CTL_INFO_DHCP_HOSTNAME:
|
|
Msg( " DHCP hostname: %s\n", cnci.dhcp_hostname );
|
|
break;
|
|
case CELL_NET_CTL_INFO_PPPOE_AUTH_NAME:
|
|
Msg( " PPPOE auth name: %s\n", cnci.pppoe_auth_name );
|
|
break;
|
|
case CELL_NET_CTL_INFO_IP_ADDRESS:
|
|
Msg( " IP address: %s\n", cnci.ip_address );
|
|
break;
|
|
case CELL_NET_CTL_INFO_NETMASK:
|
|
Msg( " Net mask: %s\n", cnci.netmask );
|
|
break;
|
|
case CELL_NET_CTL_INFO_DEFAULT_ROUTE:
|
|
Msg( " Default route: %s\n", cnci.default_route );
|
|
break;
|
|
case CELL_NET_CTL_INFO_PRIMARY_DNS:
|
|
Msg( " Primary DNS: %s\n", cnci.primary_dns );
|
|
break;
|
|
case CELL_NET_CTL_INFO_SECONDARY_DNS:
|
|
Msg( " Secondary DNS: %s\n", cnci.secondary_dns );
|
|
break;
|
|
case CELL_NET_CTL_INFO_HTTP_PROXY_CONFIG:
|
|
Msg( " HTTP proxy config: %u\n", cnci.http_proxy_config );
|
|
break;
|
|
case CELL_NET_CTL_INFO_HTTP_PROXY_SERVER:
|
|
Msg( " HTTP proxy server: %s\n", cnci.http_proxy_server );
|
|
break;
|
|
case CELL_NET_CTL_INFO_HTTP_PROXY_PORT:
|
|
Msg( " HTTP proxy port: %d\n", cnci.http_proxy_port );
|
|
break;
|
|
case CELL_NET_CTL_INFO_UPNP_CONFIG:
|
|
Msg( " UPNP config: %u\n", cnci.upnp_config );
|
|
break;
|
|
default:
|
|
Msg( " UNKNOWNNETDATA[%d]: [", iCellInfo );
|
|
NET_ConPrintByteStream( reinterpret_cast< const uint8* >( &cnci ), sizeof( cnci ) );
|
|
Msg( " ]\n" );
|
|
break;
|
|
}
|
|
}
|
|
Msg( "================================================\n" );
|
|
// -- end CELL network debug information
|
|
|
|
net_local_adr.SetFromString( "127.0.0.1" );
|
|
if ( CELL_OK == cellNetCtlGetInfo( CELL_NET_CTL_INFO_IP_ADDRESS, &cnci ) )
|
|
net_local_adr.SetFromString( cnci.ip_address );
|
|
#else
|
|
char buff[512];
|
|
|
|
// If we have changed the ip var from the command line, use that instead.
|
|
if ( Q_strcmp(ipname.GetString(), "localhost") )
|
|
{
|
|
Q_strncpy(buff, ipname.GetString(), sizeof( buff ) ); // use IP set with ipname
|
|
}
|
|
else
|
|
{
|
|
#if defined( LINUX )
|
|
// Run the systems ifconfig call to scan for an eth0 address so we don't show only the machine's loopback address
|
|
//
|
|
// note: This block simply grabs and prints out the IP address to the TTY stream
|
|
// the rest of the port/network information is printed in NET_Config()
|
|
|
|
FILE * fp = popen("ifconfig", "r");
|
|
if (fp)
|
|
{
|
|
char *curLine=NULL;
|
|
size_t n;
|
|
bool lastWasEth0 = false;
|
|
while ((getline(&curLine, &n, fp) > 0) && curLine)
|
|
{
|
|
// loop through each line returned from ifconfig
|
|
if( strstr(curLine, "Link encap:") )
|
|
{
|
|
if(strstr(curLine, "eth0") )
|
|
lastWasEth0 = true; // this is part of the entry we want, the next IP after this is the right one
|
|
else
|
|
lastWasEth0 = false;
|
|
}
|
|
|
|
if (lastWasEth0 && (curLine = strstr(curLine, "inet addr:") ))
|
|
{
|
|
curLine+=10; // skip past the eth0 lable and blank space to the address we want
|
|
char* curChar = strchr(curLine, ' ');
|
|
if ( curChar )
|
|
{
|
|
*curChar ='\0';
|
|
Msg("Network: IP %s ", curLine);
|
|
}
|
|
}
|
|
}
|
|
pclose(fp);
|
|
}
|
|
else
|
|
{
|
|
Msg( "Network: <failed to find IP> " );
|
|
}
|
|
#endif // LINUX
|
|
|
|
gethostname( buff, sizeof(buff) ); // get own IP address
|
|
buff[sizeof(buff)-1] = 0; // Ensure that it doesn't overrun the buffer
|
|
}
|
|
|
|
NET_StringToAdr (buff, &net_local_adr);
|
|
#endif
|
|
|
|
int ipaddr = ( net_local_adr.ip[0] << 24 ) +
|
|
( net_local_adr.ip[1] << 16 ) +
|
|
( net_local_adr.ip[2] << 8 ) +
|
|
net_local_adr.ip[3];
|
|
|
|
hostip.SetValue( ipaddr );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
NET_IsConfigured
|
|
|
|
Is winsock ip initialized?
|
|
====================
|
|
*/
|
|
bool NET_IsMultiplayer( void )
|
|
{
|
|
return net_multiplayer;
|
|
}
|
|
|
|
bool NET_IsDedicated( void )
|
|
{
|
|
return net_dedicated;
|
|
}
|
|
|
|
#ifdef SERVER_XLSP
|
|
bool NET_IsDedicatedForXbox( void )
|
|
{
|
|
return net_dedicated && net_dedicatedForXbox;
|
|
}
|
|
#endif
|
|
|
|
#ifdef _X360
|
|
#include "iengine.h"
|
|
static FileHandle_t g_fh;
|
|
void NET_LogServerStatus( void )
|
|
{
|
|
if ( !g_fh )
|
|
return;
|
|
|
|
static float fNextTime = 0.f;
|
|
float fCurrentTime = eng->GetCurTime();
|
|
|
|
if ( fCurrentTime >= fNextTime )
|
|
{
|
|
fNextTime = fCurrentTime + net_loginterval.GetFloat();
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
int numChannels = s_NetChannels.Count();
|
|
|
|
if ( numChannels == 0 )
|
|
{
|
|
ConMsg( "No active net channels.\n" );
|
|
return;
|
|
}
|
|
|
|
enum
|
|
{
|
|
NET_LATENCY,
|
|
NET_LOSS,
|
|
NET_PACKETS_IN,
|
|
NET_PACKETS_OUT,
|
|
NET_CHOKE_IN,
|
|
NET_CHOKE_OUT,
|
|
NET_FLOW_IN,
|
|
NET_FLOW_OUT,
|
|
NET_TOTAL_IN,
|
|
NET_TOTAL_OUT,
|
|
NET_LAST,
|
|
};
|
|
float fStats[NET_LAST] = {0.f};
|
|
|
|
for ( int i = 0; i < numChannels; ++i )
|
|
{
|
|
INetChannel *chan = s_NetChannels[i];
|
|
fStats[NET_LATENCY] += chan->GetAvgLatency(FLOW_OUTGOING);
|
|
fStats[NET_LOSS] += chan->GetAvgLoss(FLOW_INCOMING);
|
|
fStats[NET_PACKETS_IN] += chan->GetAvgPackets(FLOW_INCOMING);
|
|
fStats[NET_PACKETS_OUT] += chan->GetAvgPackets(FLOW_OUTGOING);
|
|
fStats[NET_CHOKE_IN] += chan->GetAvgChoke(FLOW_INCOMING);
|
|
fStats[NET_CHOKE_OUT] += chan->GetAvgChoke(FLOW_OUTGOING);
|
|
fStats[NET_FLOW_IN] += chan->GetAvgData(FLOW_INCOMING);
|
|
fStats[NET_FLOW_OUT] += chan->GetAvgData(FLOW_OUTGOING);
|
|
fStats[NET_TOTAL_IN] += chan->GetTotalData(FLOW_INCOMING);
|
|
fStats[NET_TOTAL_OUT] += chan->GetTotalData(FLOW_OUTGOING);
|
|
}
|
|
|
|
for ( int i = 0; i < NET_LAST; ++i )
|
|
{
|
|
fStats[i] /= numChannels;
|
|
}
|
|
|
|
const unsigned int size = 128;
|
|
char msg[size];
|
|
Q_snprintf( msg, size, "%.0f,%d,%.0f,%.0f,%.0f,%.1f,%.1f,%.1f,%.1f,%.1f\n",
|
|
fCurrentTime,
|
|
numChannels,
|
|
fStats[NET_LATENCY],
|
|
fStats[NET_LOSS],
|
|
fStats[NET_PACKETS_IN],
|
|
fStats[NET_PACKETS_OUT],
|
|
fStats[NET_FLOW_IN]/1024.0f,
|
|
fStats[NET_FLOW_OUT]/1024.0f,
|
|
fStats[NET_CHOKE_IN],
|
|
fStats[NET_CHOKE_OUT]
|
|
);
|
|
|
|
g_pFileSystem->Write( msg, Q_strlen( msg ), g_fh );
|
|
}
|
|
|
|
void NET_LogServerCallback( IConVar *pConVar, const char *pOldString, float flOldValue )
|
|
{
|
|
ConVarRef var( pConVar );
|
|
|
|
if ( var.GetBool() )
|
|
{
|
|
if ( g_fh )
|
|
{
|
|
g_pFileSystem->Close( g_fh );
|
|
g_fh = 0;
|
|
}
|
|
|
|
g_fh = g_pFileSystem->Open( "dump.csv", "wt" );
|
|
if ( !g_fh )
|
|
{
|
|
Msg( "Failed to open log file\n" );
|
|
pConVar->SetValue( 0 );
|
|
return;
|
|
}
|
|
|
|
char msg[128];
|
|
Q_snprintf( msg, 128, "Time,Channels,Latency,Loss,Packets In,Packets Out,Flow In(kB/s),Flow Out(kB/s),Choke In,Choke Out\n" );
|
|
g_pFileSystem->Write( msg, Q_strlen( msg ), g_fh );
|
|
}
|
|
else
|
|
{
|
|
if ( g_fh )
|
|
{
|
|
g_pFileSystem->Close( g_fh );
|
|
g_fh = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
====================
|
|
NET_SetTime
|
|
|
|
Updates net_time
|
|
====================
|
|
*/
|
|
void NET_SetTime( double realtime )
|
|
{
|
|
static double s_last_realtime = 0;
|
|
|
|
double frametime = realtime - s_last_realtime;
|
|
s_last_realtime = realtime;
|
|
|
|
if ( frametime > 1.0f )
|
|
{
|
|
// if we have very long frame times because of loading stuff
|
|
// don't apply that to net time to avoid unwanted timeouts
|
|
frametime = 1.0f;
|
|
}
|
|
else if ( frametime < 0.0f )
|
|
{
|
|
frametime = 0.0f;
|
|
}
|
|
|
|
#if !defined( DEDICATED )
|
|
// adjust network time so fakelag works with host_timescale
|
|
net_time += frametime * g_pEngineToolInternal->GetTimescale();
|
|
#else
|
|
net_time += frametime * sv.GetTimescale();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_RunFrame
|
|
|
|
RunFrame must be called each system frame before reading/sending on any socket
|
|
====================
|
|
*/
|
|
void NET_RunFrame( double realtime )
|
|
{
|
|
NET_SetTime( realtime );
|
|
|
|
RCONServer().RunFrame();
|
|
|
|
#ifdef ENABLE_RPT
|
|
RPTServer().RunFrame();
|
|
#endif
|
|
|
|
Con_RunFrame();
|
|
#ifndef DEDICATED
|
|
RCONClient().RunFrame();
|
|
#ifdef ENABLE_RPT
|
|
RPTClient().RunFrame();
|
|
#endif
|
|
#endif
|
|
#ifdef _X360
|
|
if ( net_logserver.GetInt() )
|
|
{
|
|
NET_LogServerStatus();
|
|
}
|
|
#endif
|
|
|
|
if ( g_pMatchFramework )
|
|
{
|
|
g_pMatchFramework->RunFrame();
|
|
}
|
|
|
|
if ( !NET_IsMultiplayer() || net_notcp )
|
|
return;
|
|
|
|
// process TCP sockets:
|
|
for ( int i=0; i< net_sockets.Count(); i++ )
|
|
{
|
|
if ( net_sockets[i].hTCP && net_sockets[i].bListening )
|
|
{
|
|
NET_ProcessListen( i );
|
|
}
|
|
}
|
|
|
|
NET_ProcessPending();
|
|
}
|
|
|
|
void NET_ClearLoopbackBuffers()
|
|
{
|
|
for (int i = 0; i < LOOPBACK_SOCKETS; i++)
|
|
{
|
|
loopback_t *loop = NULL;
|
|
|
|
while ( s_LoopBacks[i].PopItem( &loop ) )
|
|
{
|
|
if ( loop->data && loop->data != loop->defbuffer )
|
|
{
|
|
delete [] loop->data;
|
|
}
|
|
delete loop;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NET_ConfigLoopbackBuffers( bool bAlloc )
|
|
{
|
|
NET_ClearLoopbackBuffers();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_Config
|
|
|
|
A single player game will only use the loopback code
|
|
====================
|
|
*/
|
|
|
|
void NET_Config ( void )
|
|
{
|
|
// free anything
|
|
NET_CloseAllSockets(); // close all UDP/TCP sockets
|
|
|
|
net_time = 0.0f;
|
|
|
|
// now reconfigure
|
|
|
|
if ( net_multiplayer )
|
|
{
|
|
// don't allocate loopback buffers
|
|
NET_ConfigLoopbackBuffers( false );
|
|
|
|
// get localhost IP address
|
|
NET_GetLocalAddress();
|
|
|
|
// reopen sockets if in MP mode
|
|
NET_OpenSockets();
|
|
|
|
// setup the rcon server sockets
|
|
if ( net_dedicated || CommandLine()->FindParm( "-usercon" ) )
|
|
{
|
|
netadr_t rconAddr = net_local_adr;
|
|
rconAddr.SetPort( net_sockets[NS_SERVER].nPort );
|
|
RCONServer().SetAddress( rconAddr.ToString() );
|
|
RCONServer().CreateSocket();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// allocate loopback buffers
|
|
NET_ConfigLoopbackBuffers( true );
|
|
}
|
|
|
|
|
|
#if !defined( LINUX )
|
|
// note: linux prints out the network IP before calling this function
|
|
DevMsg( "Network: IP %s ", net_local_adr.ToString(true));
|
|
#endif
|
|
DevMsg( "mode %s, dedicated %s, ports %i SV / %i CL\n",
|
|
net_multiplayer?"MP":"SP", net_dedicated?"Yes":"No",
|
|
net_sockets[NS_SERVER].nPort, net_sockets[NS_CLIENT].nPort );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_SetDedicated
|
|
|
|
A single player game will only use the loopback code
|
|
====================
|
|
*/
|
|
|
|
void NET_SetDedicated ()
|
|
{
|
|
if ( net_noip )
|
|
{
|
|
Msg( "Warning! Dedicated not possible with -noip parameter.\n");
|
|
return;
|
|
}
|
|
|
|
net_dedicated = true;
|
|
net_dedicatedForXbox = ( CommandLine()->FindParm( "-xlsp" ) != 0 );
|
|
net_dedicatedForXboxInsecure = ( CommandLine()->FindParm( "-xlsp_insecure" ) != 0 );
|
|
}
|
|
|
|
void NET_ListenSocket( int sock, bool bListen )
|
|
{
|
|
Assert( (sock >= 0) && (sock < net_sockets.Count()) );
|
|
|
|
netsocket_t * netsock = &net_sockets[sock];
|
|
|
|
if ( netsock->hTCP )
|
|
{
|
|
NET_CloseSocket( netsock->hTCP, sock );
|
|
}
|
|
|
|
if ( !NET_IsMultiplayer() || net_notcp )
|
|
return;
|
|
|
|
if ( bListen )
|
|
{
|
|
const char * net_interface = ipname.GetString();
|
|
|
|
netsock->hTCP = NET_OpenSocket( net_interface, netsock->nPort, true );
|
|
|
|
if ( !netsock->hTCP )
|
|
{
|
|
Msg( "Warning! NET_ListenSocket failed opening socket %i, port %i.\n", sock, net_sockets[sock].nPort );
|
|
return;
|
|
}
|
|
|
|
struct sockaddr_in address;
|
|
|
|
if (!net_interface || !net_interface[0] || !Q_strcmp(net_interface, "localhost"))
|
|
{
|
|
address.sin_addr.s_addr = INADDR_ANY;
|
|
}
|
|
else
|
|
{
|
|
NET_StringToSockaddr (net_interface, (struct sockaddr *)&address);
|
|
}
|
|
|
|
address.sin_family = AF_INET;
|
|
address.sin_port = NET_HostToNetShort((short)( netsock->nPort ));
|
|
|
|
int ret;
|
|
ret = bind( netsock->hTCP, (struct sockaddr *)&address, sizeof(address) );
|
|
if ( ret == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_ListenSocket bind failed on socket %i, port %i.\n", netsock->hTCP, netsock->nPort );
|
|
return;
|
|
}
|
|
|
|
ret = listen( netsock->hTCP, TCP_MAX_ACCEPTS );
|
|
if ( ret == -1 )
|
|
{
|
|
NET_GetLastError();
|
|
Msg ("WARNING: NET_ListenSocket listen failed on socket %i, port %i.\n", netsock->hTCP, netsock->nPort );
|
|
return;
|
|
}
|
|
|
|
netsock->bListening = true;
|
|
}
|
|
}
|
|
|
|
void NET_SetMultiplayer(bool multiplayer)
|
|
{
|
|
if ( net_noip && multiplayer )
|
|
{
|
|
Msg( "Warning! Multiplayer mode not available with -noip parameter.\n");
|
|
return;
|
|
}
|
|
|
|
if ( net_dedicated && !multiplayer )
|
|
{
|
|
Msg( "Warning! Singleplayer mode not available on dedicated server.\n");
|
|
return;
|
|
}
|
|
|
|
// reconfigure if changed
|
|
if ( net_multiplayer != multiplayer )
|
|
{
|
|
net_multiplayer = multiplayer;
|
|
NET_Config();
|
|
}
|
|
|
|
// clear loopback buffer in single player mode
|
|
if ( !multiplayer )
|
|
{
|
|
NET_ClearLoopbackBuffers();
|
|
}
|
|
}
|
|
|
|
void NET_InitPostFork( void )
|
|
{
|
|
if ( CommandLine()->FindParm( "-NoQueuedPacketThread" ) )
|
|
Warning( "Found -NoQueuedPacketThread, so no queued packet thread will be created.\n" );
|
|
else
|
|
g_pQueuedPackedSender->Setup();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : bIsDedicated -
|
|
//-----------------------------------------------------------------------------
|
|
void NET_Init( bool bIsDedicated )
|
|
{
|
|
// In dedicated server mode network must be initialized post-fork
|
|
|
|
// single entry guard
|
|
{
|
|
static bool sbNetworkingIntialized = false;
|
|
|
|
if ( sbNetworkingIntialized )
|
|
{
|
|
return;
|
|
}
|
|
|
|
sbNetworkingIntialized = true;
|
|
}
|
|
|
|
if (CommandLine()->FindParm("-nodns"))
|
|
{
|
|
net_nodns = true;
|
|
}
|
|
|
|
if (CommandLine()->FindParm("-usetcp"))
|
|
{
|
|
net_notcp = false;
|
|
}
|
|
|
|
if ( IsGameConsole() || CommandLine()->FindParm( "-nohltv" ) )
|
|
{
|
|
net_nohltv = true;
|
|
}
|
|
|
|
if ( CommandLine()->FindParm( "-addhltv1" ) )
|
|
{
|
|
net_addhltv1 = true;
|
|
}
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
if ( CommandLine()->FindParm("-noreplay"))
|
|
{
|
|
net_noreplay = true;
|
|
}
|
|
#endif
|
|
|
|
if (CommandLine()->FindParm("-noip"))
|
|
{
|
|
net_noip = true;
|
|
}
|
|
else
|
|
{
|
|
#if defined( _X360 )
|
|
XOnlineCleanup();
|
|
|
|
XNetStartupParams xnsp;
|
|
memset( &xnsp, 0, sizeof( xnsp ) );
|
|
xnsp.cfgSizeOfStruct = sizeof( XNetStartupParams );
|
|
if ( X360SecureNetwork() )
|
|
{
|
|
Msg( "Xbox 360 Network: Secure.\n" );
|
|
}
|
|
else
|
|
{
|
|
// Allow cross-platform communication
|
|
xnsp.cfgFlags = XNET_STARTUP_BYPASS_SECURITY;
|
|
Warning( "Xbox 360 Network: Security Bypassed.\n" );
|
|
}
|
|
|
|
// Prepare for the number of connections required by the title
|
|
g_pMatchFramework->GetMatchTitle()->PrepareNetStartupParams( &xnsp );
|
|
|
|
INT err = XNetStartup( &xnsp );
|
|
if ( err )
|
|
{
|
|
Warning( "Error! XNetStartup() failed, error %d.\n", err);
|
|
}
|
|
else
|
|
{
|
|
Msg( "\n"
|
|
"Xbox 360 secure network initialized:\n"
|
|
" flags: 0x%08X\n"
|
|
" reg XNKID/XNKEY: %d\n"
|
|
" reg XNADDR/XNKID: %d\n"
|
|
" max UDP sockets: %d\n"
|
|
" max TCP sockets: %d\n"
|
|
" buffer size recv: %d K\n"
|
|
" buffer size send: %d K\n"
|
|
" QOS reply size: %d b\n"
|
|
" QOS timeout: %d sec\n"
|
|
" QOS retries: %d\n"
|
|
" QOS responses: %d\n"
|
|
" QOS pair wait: %d sec\n"
|
|
"\n",
|
|
xnsp.cfgFlags,
|
|
xnsp.cfgSockMaxDgramSockets,
|
|
xnsp.cfgSockMaxStreamSockets,
|
|
xnsp.cfgSockDefaultRecvBufsizeInK,
|
|
xnsp.cfgSockDefaultSendBufsizeInK,
|
|
xnsp.cfgKeyRegMax,
|
|
xnsp.cfgSecRegMax,
|
|
xnsp.cfgQosDataLimitDiv4 * 4,
|
|
xnsp.cfgQosProbeTimeoutInSeconds,
|
|
xnsp.cfgQosProbeRetries,
|
|
xnsp.cfgQosSrvMaxSimultaneousResponses,
|
|
xnsp.cfgQosPairWaitTimeInSeconds
|
|
);
|
|
|
|
// initialize winsock 2.2
|
|
WSAData wsaData = {0};
|
|
err = WSAStartup( MAKEWORD(2,2), &wsaData );
|
|
if ( err != 0 )
|
|
{
|
|
Warning( "Error! Failed to WSAStartup! err = %d.\n", err );
|
|
net_noip = true;
|
|
}
|
|
else
|
|
{
|
|
Msg( "Socket layer initialized:\n"
|
|
" wsa ver used: %d.%d\n"
|
|
" wsa ver max: %d.%d\n"
|
|
" description: %s\n"
|
|
" sys status: %s\n"
|
|
"\n",
|
|
LOBYTE( wsaData.wVersion ), HIBYTE( wsaData.wVersion ),
|
|
LOBYTE( wsaData.wHighVersion ), HIBYTE( wsaData.wHighVersion ),
|
|
wsaData.szDescription,
|
|
wsaData.szSystemStatus
|
|
);
|
|
|
|
err = XOnlineStartup();
|
|
if ( err != ERROR_SUCCESS )
|
|
{
|
|
Warning( "Error! XOnlineStartup() failed, error %d.\n", err );
|
|
}
|
|
else
|
|
{
|
|
Msg( "XOnline services started.\n\n" );
|
|
}
|
|
}
|
|
}
|
|
#elif defined( _WIN32 )
|
|
// initialize winsock 2.0
|
|
WSAData wsaData;
|
|
if ( WSAStartup( MAKEWORD(2,0), &wsaData ) != 0 )
|
|
{
|
|
ConMsg( "Error! Failed to load network socket library.\n");
|
|
net_noip = true;
|
|
}
|
|
#elif defined( _PS3 )
|
|
#if !defined( NO_STEAM )
|
|
// Steam initializes networking
|
|
if ( cellSysmoduleIsLoaded( CELL_SYSMODULE_NET ) != CELL_SYSMODULE_LOADED )
|
|
net_noip = true;
|
|
#else
|
|
int err = cellSysmoduleLoadModule( CELL_SYSMODULE_NET );
|
|
if ( err < 0 )
|
|
{
|
|
ConMsg( "Error! cellSysmoduleLoadModule error %d loading NET!\n", err );
|
|
net_noip = true;
|
|
}
|
|
else
|
|
{
|
|
Msg( "cellSysmoduleLoadModule loaded NET.\n" );
|
|
|
|
sys_net_initialize_parameter_t netParams;
|
|
memset( &netParams, 0, sizeof( netParams ) );
|
|
|
|
// Prepare for the number of connections required by the title
|
|
g_pMatchFramework->GetMatchTitle()->PrepareNetStartupParams( &netParams );
|
|
|
|
err = sys_net_initialize_network_ex( &netParams );
|
|
if ( err < 0 )
|
|
{
|
|
ConMsg( "Error! sys_net_initialize_network_ex error %d ( %d kBytes of memory allocated )!\n", err, netParams.memory_size / 1024 );
|
|
net_noip = true;
|
|
|
|
cellSysmoduleUnloadModule( CELL_SYSMODULE_NET );
|
|
}
|
|
else
|
|
{
|
|
Msg( "sys_net_initialize_network_ex succeeded ( %d kBytes of memory allocated )!\n", netParams.memory_size / 1024 );
|
|
|
|
int err = cellNetCtlInit();
|
|
|
|
// GSidhu - in case of NO_STEAM we try and init this lib twice
|
|
if ( (err < 0) && (err != CELL_NET_CTL_ERROR_NOT_TERMINATED) )
|
|
{
|
|
ConMsg( "Error! cellNetCtlInit error %d!\n", err );
|
|
net_noip = true;
|
|
|
|
// sys_net_finalize_network();
|
|
// cellSysmoduleUnloadModule( CELL_SYSMODULE_NET );
|
|
}
|
|
else
|
|
{
|
|
Msg( "cellNetCtlInit succeeded.\n\n" );
|
|
}
|
|
}
|
|
}
|
|
#endif // NO_STEAM
|
|
#endif
|
|
}
|
|
|
|
Assert( NET_MAX_PAYLOAD < (1<<NET_MAX_PAYLOAD_BITS) );
|
|
Assert( MAX_FILE_SIZE < (1<<MAX_FILE_SIZE_BITS) );
|
|
|
|
net_time = 0.0f;
|
|
|
|
//
|
|
// Process ports configuration
|
|
//
|
|
{
|
|
// Host port
|
|
int nHostPort = hostport.GetInt();
|
|
nHostPort = CommandLine()->ParmValue( "-port", nHostPort );
|
|
nHostPort = CommandLine()->ParmValue( "+port", nHostPort );
|
|
nHostPort = CommandLine()->ParmValue( "+hostport", nHostPort );
|
|
hostport.SetValue( nHostPort );
|
|
|
|
// Client port
|
|
int nClientPort = clientport.GetInt();
|
|
nClientPort = CommandLine()->ParmValue( "+clientport", nClientPort );
|
|
clientport.SetValue( nClientPort );
|
|
|
|
// HLTV ports
|
|
{
|
|
int nHltvPort = hltvport.GetInt();
|
|
nHltvPort = CommandLine()->ParmValue( "+tv_port", nHltvPort );
|
|
hltvport.SetValue( nHltvPort );
|
|
}
|
|
|
|
{
|
|
int nHltvPort1 = hltvport1.GetInt();
|
|
nHltvPort1 = CommandLine()->ParmValue( "+tv_port1", nHltvPort1 );
|
|
hltvport1.SetValue( nHltvPort1 );
|
|
}
|
|
}
|
|
|
|
// clear static stuff
|
|
net_sockets.EnsureCount( MAX_SOCKETS );
|
|
net_packets.EnsureCount( MAX_SOCKETS );
|
|
net_splitpackets.EnsureCount( MAX_SOCKETS );
|
|
|
|
for ( int i = 0; i < MAX_SOCKETS; ++i )
|
|
{
|
|
s_pLagData[i] = NULL;
|
|
Q_memset( &net_sockets[i], 0, sizeof(netsocket_t) );
|
|
}
|
|
|
|
if ( const char *ip = CommandLine()->ParmValue( "-ip" ) ) // if they had a command line option for IP
|
|
{
|
|
ipname.SetValue( ip ); // update the cvar right now, this will get overwritten by "stuffcmds" later
|
|
}
|
|
if ( const char *ip_tv = CommandLine()->ParmValue( "-ip_tv" ) ) // if they had a command line option for IP for GOTV
|
|
{
|
|
ipname_tv.SetValue( ip_tv ); // update the cvar right now, this will get overwritten by "stuffcmds" later
|
|
}
|
|
if ( const char *ip_tv1 = CommandLine()->ParmValue( "-ip_tv1" ) ) // if they had a command line option for IP for GOTV
|
|
{
|
|
ipname_tv1.SetValue( ip_tv1 ); // update the cvar right now, this will get overwritten by "stuffcmds" later
|
|
}
|
|
if ( const char *ip_relay = CommandLine()->ParmValue( "-ip_relay" ) ) // if they had a command line option for IP relay for GOTV
|
|
{
|
|
ipname_relay.SetValue( ip_relay ); // update the cvar right now, this will get overwritten by "stuffcmds" later
|
|
}
|
|
if ( const char *ip_steam = CommandLine()->ParmValue( "-ip_steam" ) ) // if they had a command line option for IP for Steam
|
|
{
|
|
ipname_steam.SetValue( ip_steam ); // update the cvar right now, this will get overwritten by "stuffcmds" later
|
|
}
|
|
|
|
if ( bIsDedicated )
|
|
{
|
|
// set dedicated MP mode
|
|
NET_SetDedicated();
|
|
}
|
|
else
|
|
{
|
|
// set SP mode
|
|
NET_ConfigLoopbackBuffers( true );
|
|
}
|
|
|
|
NET_InitParanoidMode();
|
|
|
|
NET_SetMultiplayer( !!( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_SETTING_MULTIPLAYER ) );
|
|
|
|
// Go ahead and create steam datagram client, and start measuring pings to data centers
|
|
#ifndef DEDICATED
|
|
if ( CheckInitSteamDatagramClientLib() )
|
|
{
|
|
if ( ::SteamNetworkingUtils() )
|
|
::SteamNetworkingUtils()->CheckPingDataUpToDate( 0.0f );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_Shutdown
|
|
|
|
====================
|
|
*/
|
|
void NET_Shutdown (void)
|
|
{
|
|
int nError = 0;
|
|
|
|
for (int i = 0; i < MAX_SOCKETS; i++)
|
|
{
|
|
NET_ClearLaggedList( &s_pLagData[i] );
|
|
}
|
|
|
|
g_pQueuedPackedSender->Shutdown();
|
|
|
|
net_multiplayer = false;
|
|
net_dedicated = false;
|
|
|
|
NET_CloseAllSockets();
|
|
NET_ConfigLoopbackBuffers( false );
|
|
#ifndef DEDICATED
|
|
SteamDatagramClient_Kill();
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
if ( !net_noip )
|
|
{
|
|
#if defined(_X360)
|
|
nError = XOnlineCleanup();
|
|
if ( nError != ERROR_SUCCESS )
|
|
{
|
|
Msg( "Warning! Failed to complete XOnlineCleanup = 0x%x.\n", nError );
|
|
}
|
|
#endif // _X360
|
|
|
|
nError = WSACleanup();
|
|
if ( nError )
|
|
{
|
|
Msg("Failed to complete WSACleanup = 0x%x.\n", nError );
|
|
}
|
|
}
|
|
#elif defined( _PS3 )
|
|
#if !defined( NO_STEAM )
|
|
// Steam manages networking
|
|
#else
|
|
if ( !net_noip )
|
|
{
|
|
cellNetCtlTerm();
|
|
sys_net_finalize_network();
|
|
cellSysmoduleUnloadModule( CELL_SYSMODULE_NET );
|
|
}
|
|
#endif
|
|
#endif // _WIN32
|
|
|
|
Assert( s_NetChannels.Count() == 0 );
|
|
Assert( s_PendingSockets.Count() == 0);
|
|
}
|
|
|
|
void NET_PrintChannelStatus( INetChannel * chan )
|
|
{
|
|
Msg( "NetChannel '%s':\n", chan->GetName() );
|
|
Msg( "- remote IP: %s\n", chan->GetAddress() );
|
|
Msg( "- online: %s\n", COM_FormatSeconds( chan->GetTimeConnected() ) );
|
|
Msg( "- reliable: %s\n", chan->HasPendingReliableData()?"pending data":"available" );
|
|
Msg( "- latency: %.1f, loss %.2f\n", chan->GetAvgLatency(FLOW_OUTGOING), chan->GetAvgLoss(FLOW_INCOMING) );
|
|
Msg( "- packets: in %.1f/s, out %.1f/s\n", chan->GetAvgPackets(FLOW_INCOMING), chan->GetAvgPackets(FLOW_OUTGOING) );
|
|
Msg( "- choke: in %.2f, out %.2f\n", chan->GetAvgChoke(FLOW_INCOMING), chan->GetAvgChoke(FLOW_OUTGOING) );
|
|
Msg( "- flow: in %.1f, out %.1f kB/s\n", chan->GetAvgData(FLOW_INCOMING)/1024.0f, chan->GetAvgData(FLOW_OUTGOING)/1024.0f );
|
|
Msg( "- total: in %.1f, out %.1f MB\n\n", (float)chan->GetTotalData(FLOW_INCOMING)/(1024*1024), (float)chan->GetTotalData(FLOW_OUTGOING)/(1024*1024) );
|
|
}
|
|
|
|
CON_COMMAND( net_channels, "Shows net channel info" )
|
|
{
|
|
int numChannels = s_NetChannels.Count();
|
|
|
|
if ( numChannels == 0 )
|
|
{
|
|
ConMsg( "No active net channels.\n" );
|
|
return;
|
|
}
|
|
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
for ( int i = 0; i < numChannels; i++ )
|
|
{
|
|
NET_PrintChannelStatus( s_NetChannels[i] );
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( net_start, "Inits multiplayer network sockets" )
|
|
{
|
|
net_multiplayer = true;
|
|
NET_Config();
|
|
}
|
|
|
|
CON_COMMAND( net_status, "Shows current network status" )
|
|
{
|
|
AUTO_LOCK_FM( s_NetChannels );
|
|
int numChannels = s_NetChannels.Count();
|
|
|
|
ConMsg("Net status for host %s:\n",
|
|
net_local_adr.ToString(true) );
|
|
|
|
ConMsg("- Config: %s, %s, %i connections\n",
|
|
net_multiplayer?"Multiplayer":"Singleplayer",
|
|
net_dedicated?"dedicated":"listen",
|
|
numChannels );
|
|
|
|
|
|
ConMsg("- Ports: " );
|
|
for ( int k = 0; k < MAX_SOCKETS; ++ k )
|
|
{
|
|
ConMsg( "%s%d %u, ", DescribeSocket( k ), k, NET_GetUDPPort( k ) );
|
|
}
|
|
ConMsg( "%d total.\n", MAX_SOCKETS );
|
|
|
|
if ( numChannels <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// gather statistics:
|
|
|
|
float avgLatencyOut = 0;
|
|
float avgLatencyIn = 0;
|
|
float avgPacketsOut = 0;
|
|
float avgPacketsIn = 0;
|
|
float avgLossOut = 0;
|
|
float avgLossIn = 0;
|
|
float avgDataOut = 0;
|
|
float avgDataIn = 0;
|
|
|
|
for ( int i = 0; i < numChannels; i++ )
|
|
{
|
|
CNetChan *chan = s_NetChannels[i];
|
|
|
|
avgLatencyOut += chan->GetAvgLatency(FLOW_OUTGOING);
|
|
avgLatencyIn += chan->GetAvgLatency(FLOW_INCOMING);
|
|
|
|
avgLossIn += chan->GetAvgLoss(FLOW_INCOMING);
|
|
avgLossOut += chan->GetAvgLoss(FLOW_OUTGOING);
|
|
|
|
avgPacketsIn += chan->GetAvgPackets(FLOW_INCOMING);
|
|
avgPacketsOut += chan->GetAvgPackets(FLOW_OUTGOING);
|
|
|
|
avgDataIn += chan->GetAvgData(FLOW_INCOMING);
|
|
avgDataOut += chan->GetAvgData(FLOW_OUTGOING);
|
|
}
|
|
|
|
ConMsg( "- Latency: avg out %.2fs, in %.2fs\n", avgLatencyOut/numChannels, avgLatencyIn/numChannels );
|
|
ConMsg( "- Loss: avg out %.1f, in %.1f\n", avgLossOut/numChannels, avgLossIn/numChannels );
|
|
ConMsg( "- Packets: net total out %.1f/s, in %.1f/s\n", avgPacketsOut, avgPacketsIn );
|
|
ConMsg( " per client out %.1f/s, in %.1f/s\n", avgPacketsOut/numChannels, avgPacketsIn/numChannels );
|
|
ConMsg( "- Data: net total out %.1f, in %.1f kB/s\n", avgDataOut/1024.0f, avgDataIn/1024.0f );
|
|
ConMsg( " per client out %.1f, in %.1f kB/s\n", (avgDataOut/numChannels)/1024.0f, (avgDataIn/numChannels)/1024.0f );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generic buffer compression from source into dest
|
|
// Input : *dest -
|
|
// *destLen -
|
|
// *source -
|
|
// sourceLen -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
bool NET_BufferToBufferCompress( char *dest, unsigned int *destLen, char *source, unsigned int sourceLen )
|
|
{
|
|
Assert( dest );
|
|
Assert( destLen );
|
|
Assert( source );
|
|
|
|
Q_memcpy( dest, source, sourceLen );
|
|
CLZSS s;
|
|
unsigned int uCompressedLen = 0;
|
|
byte *pbOut = s.Compress( (byte *)source, sourceLen, &uCompressedLen );
|
|
if ( pbOut && uCompressedLen > 0 && uCompressedLen <= *destLen )
|
|
{
|
|
Q_memcpy( dest, pbOut, uCompressedLen );
|
|
*destLen = uCompressedLen;
|
|
free( pbOut );
|
|
}
|
|
else
|
|
{
|
|
if ( pbOut )
|
|
{
|
|
free( pbOut );
|
|
}
|
|
Q_memcpy( dest, source, sourceLen );
|
|
*destLen = sourceLen;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generic buffer decompression from source into dest
|
|
// Input : *dest -
|
|
// *destLen -
|
|
// *source -
|
|
// sourceLen -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
bool NET_BufferToBufferDecompress( char *dest, unsigned int *destLen, char *source, unsigned int sourceLen )
|
|
{
|
|
CLZSS s;
|
|
if ( s.IsCompressed( (byte *)source ) )
|
|
{
|
|
unsigned int uDecompressedLen = s.GetActualSize( (byte *)source );
|
|
if ( uDecompressedLen > *destLen )
|
|
{
|
|
Warning( "NET_BufferToBufferDecompress with improperly sized dest buffer (%u in, %u needed)\n", *destLen, uDecompressedLen );
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
*destLen = s.SafeUncompress( (byte *)source, (byte *)dest, *destLen );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( sourceLen > *destLen )
|
|
{
|
|
Warning( "NET_BufferToBufferDecompress with improperly sized dest buffer (%u in, %u needed)\n", *destLen, sourceLen );
|
|
return false;
|
|
}
|
|
|
|
Q_memcpy( dest, source, sourceLen );
|
|
*destLen = sourceLen;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NET_SleepUntilMessages( int nMilliseconds )
|
|
{
|
|
fd_set fdset;
|
|
FD_ZERO(&fdset);
|
|
|
|
if ( !net_sockets[NS_SERVER].hUDP )
|
|
{
|
|
Sys_Sleep( nMilliseconds );
|
|
return;
|
|
}
|
|
|
|
unsigned int nSocket = (unsigned int)net_sockets[NS_SERVER].hUDP;
|
|
FD_SET( nSocket, &fdset );
|
|
struct timeval tv = { 0 };
|
|
tv.tv_usec = nMilliseconds * 1000;
|
|
select( nSocket + 1, &fdset, NULL, NULL, &tv );
|
|
}
|
|
|
|
bool NET_GetPublicAdr( netadr_t &adr )
|
|
{
|
|
bool bRet = false;
|
|
unsigned short port = NET_GetUDPPort( NS_SERVER );
|
|
if ( net_public_adr.GetString()[ 0 ] )
|
|
{
|
|
bRet = true;
|
|
adr.SetType( NA_IP );
|
|
adr.SetFromString( net_public_adr.GetString() );
|
|
if ( adr.GetPort() == 0 )
|
|
adr.SetPort( port );
|
|
}
|
|
#if !defined( _X360 ) && !defined( NO_STEAM )
|
|
else if ( NET_IsDedicated() &&
|
|
Steam3Server().SteamGameServer()->GetPublicIP() != 0u )
|
|
{
|
|
bRet = true;
|
|
adr.SetType( NA_IP );
|
|
adr.SetIPAndPort( Steam3Server().SteamGameServer()->GetPublicIP(), port );
|
|
}
|
|
#endif
|
|
return bRet;
|
|
}
|
|
|
|
void NET_SteamDatagramServerListen()
|
|
{
|
|
// Receiving on steam datagram transport?
|
|
// We only open one interface object (corresponding to one UDP port).
|
|
// The other "sockets" are different channels on this interface
|
|
if ( sv_steamdatagramtransport_port.GetInt() == 0 )
|
|
return;
|
|
if ( g_pSteamDatagramGameserver )
|
|
return;
|
|
|
|
SteamDatagramErrMsg errMsg;
|
|
EResult result;
|
|
g_pSteamDatagramGameserver = SteamDatagram_GameserverListen( GetSteamUniverse(), sv_steamdatagramtransport_port.GetInt(), &result, errMsg );
|
|
if ( g_pSteamDatagramGameserver )
|
|
{
|
|
Msg( "Listening for Steam datagram transport on port %d\n", sv_steamdatagramtransport_port.GetInt() );
|
|
}
|
|
else
|
|
{
|
|
Warning( "SteamDatagram_GameserverListen failed with error code %d. %s\n", result, errMsg );
|
|
|
|
// Clear the convar so we don't advertise that we are listening!
|
|
sv_steamdatagramtransport_port.SetValue( 0 );
|
|
}
|
|
}
|
|
|
|
void NET_TerminateConnection( int sock, const ns_address &peer )
|
|
{
|
|
#if defined( USE_STEAM_SOCKETS )
|
|
if ( peer.IsType<netadr_t)() )
|
|
{
|
|
uint64 steamIDRemote = g_pSteamSocketMgr->GetSteamIDForRemote( peer.AsType<netadr_t>() );
|
|
NET_TerminateSteamConnection( steamIDRemote );
|
|
}
|
|
#endif
|
|
#ifndef DEDICATED
|
|
if ( peer == g_addrSteamDatagramProxiedGameServer )
|
|
CloseSteamDatagramClientConnection();
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Cryptography support code
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef Verify
|
|
#undef Verify
|
|
#endif
|
|
|
|
#define bswap_16 __bswap_16
|
|
#define bswap_64 __bswap_64
|
|
|
|
#include "cryptlib.h"
|
|
#include "rsa.h"
|
|
#include "osrng.h"
|
|
|
|
using namespace CryptoPP;
|
|
typedef AutoSeededX917RNG<AES> CAutoSeededRNG;
|
|
|
|
// list of auto-seeded RNG pointers
|
|
// these are very expensive to construct, so it makes sense to cache them
|
|
CTSList<CAutoSeededRNG>&
|
|
GlobalRNGList()
|
|
{
|
|
static CTSList<CAutoSeededRNG> g_tslistPAutoSeededRNG;
|
|
return g_tslistPAutoSeededRNG;
|
|
}
|
|
|
|
// to avoid deconstructor order issuses we allow to manually free the list
|
|
void FreeListRNG()
|
|
{
|
|
GlobalRNGList().Purge();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: thread-safe access to a pool of cryptoPP random number generators
|
|
//-----------------------------------------------------------------------------
|
|
class CPoolAllocatedRNG
|
|
{
|
|
public:
|
|
CPoolAllocatedRNG()
|
|
{
|
|
m_pRNGNode = GlobalRNGList().Pop();
|
|
if ( !m_pRNGNode )
|
|
{
|
|
m_pRNGNode = new CTSList<CAutoSeededRNG>::Node_t;
|
|
}
|
|
}
|
|
|
|
~CPoolAllocatedRNG()
|
|
{
|
|
GlobalRNGList().Push( m_pRNGNode );
|
|
}
|
|
|
|
CAutoSeededRNG &GetRNG()
|
|
{
|
|
return m_pRNGNode->elem;
|
|
}
|
|
|
|
private:
|
|
CTSList<CAutoSeededRNG>::Node_t *m_pRNGNode;
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Encrypted networking
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool NET_CryptVerifyServerCertificateAndAllocateSessionKey( bool bOfficial, const ns_address &from,
|
|
const byte *pchKeyPub, int numKeyPub,
|
|
const byte *pchKeySgn, int numKeySgn,
|
|
byte **pbAllocatedKey, int *pnAllocatedCryptoBlockSize )
|
|
{
|
|
static const byte CsgoMasterPublicKey[] = { 0 }; // Removed for partner depot
|
|
|
|
// For now, only IPv4 addresses allowed. Shouldn't be too hard to figure out how to
|
|
// generate blocks to sign for other types of addresses
|
|
uint32 unCertIP = 0;
|
|
switch ( from.GetAddressType() )
|
|
{
|
|
case NSAT_NETADR:
|
|
unCertIP = from.AsType<netadr_t>().GetIPHostByteOrder();
|
|
break;
|
|
case NSAT_PROXIED_GAMESERVER:
|
|
{
|
|
unCertIP = SteamNetworkingUtils()->GetIPForServerSteamIDFromTicket( from.m_steamID.GetSteamID() );
|
|
if ( unCertIP == 0 )
|
|
{
|
|
Warning( "NET_CryptVerifyServerCertificateAndAllocateSessionKey - cannot check signature for proxied server '%s', because we don't have an SDR ticket to that server.\n", ns_address_render( from ).String() );
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if ( unCertIP == 0 )
|
|
{
|
|
Warning( "NET_CryptVerifyServerCertificateAndAllocateSessionKey - cannot check signature for '%s', cannot determine IP to use\n", ns_address_render( from ).String() );
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Verify certificate
|
|
//
|
|
bool bCertificateValidated = false;
|
|
for ( int numAddrBits = 32; ( numAddrBits >= 16 ) && !bCertificateValidated; -- numAddrBits )
|
|
{
|
|
CUtlBuffer bufSignature;
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePublicKey( CsgoMasterPublicKey, Q_ARRAYSIZE( CsgoMasterPublicKey ), true );
|
|
RSASSA_PKCS1v15_SHA_Verifier pub( stringSourcePublicKey );
|
|
CUtlBuffer bufDataFile;
|
|
bufDataFile.EnsureCapacity( numKeyPub + 20 );
|
|
bufDataFile.Put( pchKeyPub, numKeyPub );
|
|
|
|
netadr_t adrMasked( unCertIP & ( (~0) << (32-numAddrBits) ), 0 );
|
|
char chBuffer[20] = {};
|
|
V_sprintf_safe( chBuffer, "%s/%u", adrMasked.ToString( true ), numAddrBits );
|
|
bufDataFile.Put( chBuffer, V_strlen( chBuffer ) );
|
|
|
|
bCertificateValidated = pub.VerifyMessage( ( byte* ) bufDataFile.Base(), bufDataFile.TellPut(), pchKeySgn, numKeySgn );
|
|
#ifdef _DEBUG
|
|
DevMsg( "NET_CryptVerifyServerCertificateAndAllocateSessionKey: VerifyMessage for %s %s\n",
|
|
chBuffer, bCertificateValidated ? "succeeded" : "failed" );
|
|
#endif
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyServerCertificateAndAllocateSessionKey: VerifyMessage threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
#endif
|
|
}
|
|
catch ( ... )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyServerCertificateAndAllocateSessionKey: VerifyMessage threw unknown exception\n" );
|
|
#endif
|
|
}
|
|
}
|
|
if ( !bCertificateValidated )
|
|
return false;
|
|
|
|
//
|
|
// Allocate client key
|
|
//
|
|
float flTime = Plat_FloatTime();
|
|
RandomSeed( * reinterpret_cast< int * >( &flTime ) );
|
|
byte ubClientKey[NET_CRYPT_KEY_LENGTH];
|
|
for ( int j = 0; j < NET_CRYPT_KEY_LENGTH; ++j )
|
|
{
|
|
ubClientKey[ j ] = ( byte ) ( unsigned int ) RandomInt( 0, 255 );
|
|
}
|
|
|
|
//
|
|
// Encrypt client key using the public key
|
|
//
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePublicKey( pchKeyPub, numKeyPub, true );
|
|
RSAES_OAEP_SHA_Encryptor rsaEncryptor( stringSourcePublicKey );
|
|
|
|
// calculate how many blocks of encryption will we need to do
|
|
AssertFatal( rsaEncryptor.FixedMaxPlaintextLength() <= UINT32_MAX );
|
|
uint32 cBlocks = 1 + ( ( NET_CRYPT_KEY_LENGTH - 1 ) / ( uint32 ) rsaEncryptor.FixedMaxPlaintextLength() );
|
|
// calculate how big the output will be
|
|
AssertFatal( rsaEncryptor.FixedCiphertextLength() <= UINT32_MAX / cBlocks );
|
|
uint32 cubCipherText = cBlocks * ( uint32 ) rsaEncryptor.FixedCiphertextLength();
|
|
|
|
// ensure there is sufficient room in output buffer for result
|
|
byte *pbResult = new byte[ NET_CRYPT_KEY_LENGTH + cubCipherText ];
|
|
Q_memcpy( pbResult, ubClientKey, NET_CRYPT_KEY_LENGTH );
|
|
|
|
*pbAllocatedKey = pbResult;
|
|
*pnAllocatedCryptoBlockSize = cubCipherText;
|
|
|
|
// Encryption pass
|
|
uint32 cubPlaintextData = NET_CRYPT_KEY_LENGTH;
|
|
const byte *pubPlaintextData = ubClientKey;
|
|
byte *pubEncryptedData = pbResult + NET_CRYPT_KEY_LENGTH;
|
|
|
|
// encrypt the message, using as many blocks as required
|
|
CPoolAllocatedRNG rng;
|
|
for ( uint32 nBlock = 0; nBlock < cBlocks; nBlock++ )
|
|
{
|
|
// encrypt either all remaining plaintext, or maximum allowed plaintext per RSA encryption operation
|
|
uint32 cubToEncrypt = MIN( cubPlaintextData, ( uint32 ) rsaEncryptor.FixedMaxPlaintextLength() );
|
|
// encrypt the plaintext
|
|
rsaEncryptor.Encrypt( rng.GetRNG(), pubPlaintextData, cubToEncrypt, pubEncryptedData );
|
|
// adjust input and output pointers and remaining plaintext byte count
|
|
pubPlaintextData += cubToEncrypt;
|
|
cubPlaintextData -= cubToEncrypt;
|
|
pubEncryptedData += rsaEncryptor.FixedCiphertextLength();
|
|
}
|
|
Assert( 0 == cubPlaintextData ); // should have no remaining plaintext to encrypt
|
|
|
|
#ifdef _DEBUG
|
|
DevMsg( "NET_CryptVerifyServerCertificateAndAllocateSessionKey: Encrypted %u bytes key as %u bytes ciphertext\n",
|
|
NET_CRYPT_KEY_LENGTH, cubCipherText );
|
|
#endif
|
|
return true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyServerCertificateAndAllocateSessionKey: Encrypt threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
#endif
|
|
return false;
|
|
}
|
|
catch ( ... )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyServerCertificateAndAllocateSessionKey: Encrypt threw unknown exception\n" );
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool NET_CryptVerifyClientSessionKey( bool bOfficial,
|
|
const byte *pchKeyPri, int numKeyPri,
|
|
const byte *pbEncryptedKey, int numEncryptedBytes,
|
|
byte *pbPlainKey, int numPlainKeyBytes )
|
|
{
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePrivateKey( pchKeyPri, numKeyPri, true );
|
|
RSAES_OAEP_SHA_Decryptor rsaDecryptor( stringSourcePrivateKey );
|
|
|
|
// calculate how many blocks of decryption will we need to do
|
|
AssertFatal( rsaDecryptor.FixedCiphertextLength() <= UINT32_MAX );
|
|
uint32 cubFixedCiphertextLength = ( uint32 ) rsaDecryptor.FixedCiphertextLength();
|
|
|
|
// Ensure encrypted data is valid and has length that is exact multiple of 128 bytes
|
|
uint32 cubEncryptedData = numEncryptedBytes;
|
|
if ( 0 != ( cubEncryptedData % cubFixedCiphertextLength ) )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyClientSessionKey: invalid ciphertext length %d, needs to be a multiple of %d\n",
|
|
cubEncryptedData, cubFixedCiphertextLength );
|
|
#endif
|
|
return false;
|
|
}
|
|
uint32 cBlocks = cubEncryptedData / cubFixedCiphertextLength;
|
|
|
|
// calculate how big the maximum output will be
|
|
size_t cubMaxPlaintext = rsaDecryptor.MaxPlaintextLength( rsaDecryptor.FixedCiphertextLength() );
|
|
AssertFatal( cubMaxPlaintext <= UINT32_MAX / cBlocks );
|
|
uint32 cubPlaintextDataMax = cBlocks * ( uint32 ) cubMaxPlaintext;
|
|
Assert( cubPlaintextDataMax > 0 );
|
|
// ensure there is sufficient room in output buffer for result
|
|
if ( int( cubPlaintextDataMax ) >= numPlainKeyBytes )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyClientSessionKey: insufficient output buffer for decryption, needed %d got %d\n",
|
|
cubPlaintextDataMax, numPlainKeyBytes );
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// decrypt the data, using as many blocks as required
|
|
CPoolAllocatedRNG rng;
|
|
uint32 cubPlaintextData = 0;
|
|
for ( uint32 nBlock = 0; nBlock < cBlocks; nBlock++ )
|
|
{
|
|
// decrypt one block (always of fixed size)
|
|
int cubToDecrypt = cubFixedCiphertextLength;
|
|
DecodingResult decodingResult = rsaDecryptor.Decrypt( rng.GetRNG(), pbEncryptedKey, cubToDecrypt, pbPlainKey );
|
|
if ( !decodingResult.isValidCoding )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyClientSessionKey: failed to decrypt\n" );
|
|
#endif
|
|
return false;
|
|
}
|
|
// adjust input and output pointers and remaining encrypted byte count
|
|
pbEncryptedKey += cubToDecrypt;
|
|
cubEncryptedData -= cubToDecrypt;
|
|
pbPlainKey += decodingResult.messageLength;
|
|
AssertFatal( decodingResult.messageLength <= UINT32_MAX );
|
|
cubPlaintextData += ( uint32 ) decodingResult.messageLength;
|
|
}
|
|
Assert( 0 == cubEncryptedData ); // should have no remaining encrypted data to decrypt
|
|
|
|
if ( cubPlaintextData != NET_CRYPT_KEY_LENGTH )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyClientSessionKey: decrypted %u bytes when expecting %u bytes\n", cubPlaintextData, NET_CRYPT_KEY_LENGTH );
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyClientSessionKey: Decrypt threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
#endif
|
|
return false;
|
|
}
|
|
catch ( ... )
|
|
{
|
|
#ifdef _DEBUG
|
|
Warning( "NET_CryptVerifyClientSessionKey: Decrypt threw unknown exception\n" );
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool NET_CryptGetNetworkCertificate( ENetworkCertificate_t eType, const byte **pbData, int *pnumBytes )
|
|
{
|
|
static char const *s_szCertificateFile = CommandLine()->ParmValue( "-certificate", ( char const * ) NULL );
|
|
if ( !s_szCertificateFile )
|
|
return false;
|
|
|
|
static bool s_bCertificateFilesLoaded = false;
|
|
static CUtlBuffer bufCertificate;
|
|
static int s_nCertificateOffset[ k_ENetworkCertificate_Max ] = {};
|
|
static int s_nCertificateLength[ k_ENetworkCertificate_Max ] = {};
|
|
if ( !s_bCertificateFilesLoaded )
|
|
{
|
|
s_bCertificateFilesLoaded = true;
|
|
|
|
if ( !g_pFullFileSystem->ReadFile( s_szCertificateFile, NULL, bufCertificate ) ||
|
|
!bufCertificate.Base() || ( bufCertificate.Size() < ( k_ENetworkCertificate_Max + 1 ) * 3 * sizeof( int ) ) )
|
|
{
|
|
Warning( "NET_CryptGetNetworkCertificate failed to load certificate '%s'\n", s_szCertificateFile );
|
|
Plat_ExitProcess( 0 ); // we must exit process, but we will skip writing the core dump
|
|
return false;
|
|
}
|
|
|
|
if ( V_memcmp( bufCertificate.Base(), "CSv1", 4 ) )
|
|
{
|
|
Warning( "NET_CryptGetNetworkCertificate certificate version mismatch '%s'\n", s_szCertificateFile );
|
|
Plat_ExitProcess( 0 ); // we must exit process, but we will skip writing the core dump
|
|
return false;
|
|
}
|
|
|
|
bufCertificate.GetInt(); // CSv1
|
|
int nTOC = bufCertificate.GetInt();
|
|
if ( nTOC != k_ENetworkCertificate_Max )
|
|
{
|
|
Warning( "NET_CryptGetNetworkCertificate certificate TOC length mismatch '%s'\n", s_szCertificateFile );
|
|
Plat_ExitProcess( 0 ); // we must exit process, but we will skip writing the core dump
|
|
return false;
|
|
}
|
|
|
|
for ( int j = 0; j < k_ENetworkCertificate_Max; ++ j )
|
|
{
|
|
int nFileID = bufCertificate.GetInt();
|
|
if ( nFileID != j )
|
|
{
|
|
Warning( "NET_CryptGetNetworkCertificate certificate TOC entry %d invalid in '%s'\n", j, s_szCertificateFile );
|
|
Plat_ExitProcess( 0 ); // we must exit process, but we will skip writing the core dump
|
|
return false;
|
|
}
|
|
|
|
s_nCertificateOffset[j] = bufCertificate.GetInt();
|
|
s_nCertificateLength[j] = bufCertificate.GetInt();
|
|
}
|
|
}
|
|
|
|
*pbData = ( ( const byte * ) bufCertificate.Base() ) + s_nCertificateOffset[eType];
|
|
*pnumBytes = s_nCertificateLength[eType];
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
CON_COMMAND( net_encrypt_key_generate, "Generate a public/private keypair" )
|
|
{
|
|
if ( args.ArgC() <= 2 )
|
|
{
|
|
Warning( "Usage: net_encrypt_key_generate <numbits> <filename>\n" );
|
|
return;
|
|
}
|
|
|
|
uint32 cKeyBits = Q_atoi( args.Arg( 1 ) );
|
|
|
|
bool bSuccess = false;
|
|
std::string strPrivateKey;
|
|
std::string strPublicKey;
|
|
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
// generate private key
|
|
StringSink stringSinkPrivateKey( strPrivateKey );
|
|
CPoolAllocatedRNG rng;
|
|
RSAES_OAEP_SHA_Decryptor priv( rng.GetRNG(), cKeyBits );
|
|
priv.DEREncode( stringSinkPrivateKey );
|
|
|
|
// generate public key
|
|
StringSink stringSinkPublicKey( strPublicKey );
|
|
RSAES_OAEP_SHA_Encryptor pub( priv );
|
|
pub.DEREncode( stringSinkPublicKey );
|
|
bSuccess = true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Warning( "net_encrypt_key_generate: crypto++ threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
Warning( "net_encrypt_key_generate: crypto++ threw unknown exception\n" );
|
|
}
|
|
|
|
if ( bSuccess )
|
|
{
|
|
char chFile[256];
|
|
|
|
CUtlBuffer bufPrivate( strPrivateKey.c_str(), strPrivateKey.length(), CUtlBuffer::READ_ONLY );
|
|
V_sprintf_safe( chFile, "%s.private", args.Arg( 2 ) );
|
|
if ( !g_pFullFileSystem->WriteFile( chFile, NULL, bufPrivate ) )
|
|
{
|
|
Warning( "net_encrypt_key_generate: failed to write %u bits keypair file '%s'\n", cKeyBits, chFile );
|
|
return;
|
|
}
|
|
|
|
CUtlBuffer bufPublic( strPublicKey.c_str(), strPublicKey.length(), CUtlBuffer::READ_ONLY );
|
|
V_sprintf_safe( chFile, "%s.public", args.Arg( 2 ) );
|
|
if ( !g_pFullFileSystem->WriteFile( chFile, NULL, bufPublic ) )
|
|
{
|
|
Warning( "net_encrypt_key_generate: failed to write %u bits keypair file '%s'\n", cKeyBits, chFile );
|
|
return;
|
|
}
|
|
|
|
Msg( "net_encrypt_key_generate: wrote %u bits keypair files '%s.private/public'\n", cKeyBits, args.Arg( 2 ) );
|
|
}
|
|
else
|
|
{
|
|
Warning( "net_encrypt_key_generate: failed to generate %u bits keypair files '%s.private/public'\n", cKeyBits, args.Arg( 2 ) );
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( net_encrypt_key_signature, "Compute key signature for the payloads" )
|
|
{
|
|
if ( args.ArgC() <= 4 )
|
|
{
|
|
Warning( "Usage: net_encrypt_key_signature <file.privatekey> <file.payload> <string.payload> <output.file>\n" );
|
|
return;
|
|
}
|
|
|
|
bool bRet = false;
|
|
CUtlBuffer bufSignature;
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
CUtlBuffer bufPrivateKey;
|
|
if ( !g_pFullFileSystem->ReadFile( args.Arg( 1 ), NULL, bufPrivateKey ) )
|
|
{
|
|
Warning( "net_encrypt_key_signature: failed to read private key file '%s'\n", args.Arg( 1 ) );
|
|
return;
|
|
}
|
|
|
|
CUtlBuffer bufDataFile;
|
|
if ( !g_pFullFileSystem->ReadFile( args.Arg( 2 ), NULL, bufDataFile ) )
|
|
{
|
|
Warning( "net_encrypt_key_signature: failed to read data file '%s'\n", args.Arg( 2 ) );
|
|
return;
|
|
}
|
|
|
|
char const *szStringPayload = args.Arg( 3 );
|
|
int nStringPayloadLength = Q_strlen( szStringPayload );
|
|
if ( nStringPayloadLength > 0 )
|
|
bufDataFile.Put( szStringPayload, nStringPayloadLength );
|
|
|
|
StringSource stringSourcePrivateKey( ( byte * ) bufPrivateKey.Base(), bufPrivateKey.TellPut(), true );
|
|
RSASSA_PKCS1v15_SHA_Signer rsaSigner( stringSourcePrivateKey );
|
|
CPoolAllocatedRNG rng;
|
|
|
|
bufSignature.EnsureCapacity( rsaSigner.MaxSignatureLength() );
|
|
{
|
|
size_t len = rsaSigner.SignMessage( rng.GetRNG(), ( byte * ) bufDataFile.Base(), bufDataFile.TellPut(), ( byte * ) bufSignature.Base() );
|
|
bufSignature.SeekPut( CUtlBuffer::SEEK_HEAD, ( int32 ) ( uint32 ) len );
|
|
bRet = true;
|
|
Msg( "net_encrypt_key_signature: generated %u bytes signature for payload data +%u=%u bytes\n", bufSignature.TellPut(), nStringPayloadLength, bufDataFile.TellPut() );
|
|
}
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Warning( "net_encrypt_key_signature: SignMessage threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
Warning( "net_encrypt_key_signature: SignMessage threw unknown exception\n" );
|
|
}
|
|
|
|
if ( bRet )
|
|
{
|
|
if ( !g_pFullFileSystem->WriteFile( args.Arg( 4 ), NULL, bufSignature ) )
|
|
{
|
|
Warning( "net_encrypt_key_signature: failed to write file '%s'\n", args.Arg( 4 ) );
|
|
return;
|
|
}
|
|
|
|
Msg( "net_encrypt_key_generate: wrote %u bytes signature file '%s'\n", bufSignature.TellPut(), args.Arg( 4 ) );
|
|
}
|
|
else
|
|
{
|
|
Warning( "net_encrypt_key_signature: failed\n" );
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( net_encrypt_key_compress, "Compress all key signatures into a single file" )
|
|
{
|
|
if ( args.ArgC() <= 1 )
|
|
{
|
|
Warning( "Usage: net_encrypt_key_compress <file>\n" );
|
|
return;
|
|
}
|
|
|
|
CUtlBuffer bufComposite;
|
|
|
|
char const * arrFiles[] = { "public", "private", "signature" }; // ENetworkCertificate_t order
|
|
CUtlBuffer bufData[ Q_ARRAYSIZE( arrFiles ) ];
|
|
for ( int j = 0; j < Q_ARRAYSIZE( arrFiles ); ++ j )
|
|
{
|
|
char chFile[ 1024 ] = {};
|
|
V_sprintf_safe( chFile, "%s.%s", args.Arg( 1 ), arrFiles[j] );
|
|
if ( !g_pFullFileSystem->ReadFile( chFile, NULL, bufData[j] ) )
|
|
{
|
|
Warning( "net_encrypt_key_compress: failed to read data file '%s'\n", chFile );
|
|
return;
|
|
}
|
|
}
|
|
|
|
bufComposite.Put( "CSv1", 4 );
|
|
bufComposite.PutInt( Q_ARRAYSIZE( arrFiles ) );
|
|
int nDataOffsetBase = bufComposite.TellPut() + Q_ARRAYSIZE( arrFiles )*3*4;
|
|
int nDataOffset = nDataOffsetBase;
|
|
for ( int j = 0; j < Q_ARRAYSIZE( arrFiles ); ++ j )
|
|
{
|
|
bufComposite.PutInt( j ); // file ID
|
|
bufComposite.PutInt( nDataOffset ); // offset
|
|
bufComposite.PutInt( bufData[j].TellPut() ); // length
|
|
nDataOffset += bufData[j].TellPut();
|
|
}
|
|
if ( bufComposite.TellPut() != nDataOffsetBase )
|
|
{
|
|
Warning( "net_encrypt_key_compress: failed to align composite TOC for '%s'\n", args.Arg( 1 ) );
|
|
return;
|
|
}
|
|
for ( int j = 0; j < Q_ARRAYSIZE( arrFiles ); ++ j )
|
|
{
|
|
bufComposite.Put( bufData[j].Base(), bufData[j].TellPut() );
|
|
}
|
|
|
|
if ( !g_pFullFileSystem->WriteFile( args.Arg( 1 ), NULL, bufComposite ) )
|
|
{
|
|
Warning( "net_encrypt_key_compress: failed to write file '%s'\n", args.Arg( 1 ) );
|
|
return;
|
|
}
|
|
|
|
for ( int j = 0; j < Q_ARRAYSIZE( arrFiles ); ++ j )
|
|
{
|
|
char chFile[ 1024 ] = {};
|
|
V_sprintf_safe( chFile, "%s.%s", args.Arg( 1 ), arrFiles[j] );
|
|
g_pFullFileSystem->RemoveFile( chFile );
|
|
}
|
|
|
|
Msg( "net_encrypt_key_compress: compressed file '%s' (%u bytes)\n", args.Arg( 1 ), bufComposite.TellPut() );
|
|
}
|
|
|
|
CON_COMMAND( net_encrypt_key_make_clusters, "Generate certificates and their signatures using master key" )
|
|
{
|
|
if ( args.ArgC() <= 2 )
|
|
{
|
|
Warning( "Usage: net_encrypt_key_make_clusters <numbits> <file.privatekey>\n" );
|
|
return;
|
|
}
|
|
|
|
char const * arrAddressMasks[] = {
|
|
// "atl-1", "162.254.199.0/25" ,
|
|
// "dxb-1", "185.25.183.0/25" ,
|
|
// "eat-1", "192.69.96.0/23" ,
|
|
// "eat-2", "192.69.96.0/23" ,
|
|
// "eat-3", "192.69.96.0/23" ,
|
|
// "eat-4", "192.69.96.0/23" ,
|
|
// "gru-4", "205.185.194.0/24" ,
|
|
// "iad-1", "208.78.164.90/23" ,
|
|
// "iad-2", "208.78.164.0/23" ,
|
|
// "iad-3", "208.78.164.0/23" ,
|
|
// "iad-4", "208.78.166.0/24" ,
|
|
// "lax-1", "162.254.194.0/24" ,
|
|
// "lux-1", "146.66.152.0/23" ,
|
|
// "lux-2", "146.66.152.0/23" ,
|
|
// "lux-3", "146.66.152.0/23" ,
|
|
// "lux-4", "146.66.158.0/23" ,
|
|
// "lux-5", "146.66.158.0/23" ,
|
|
// "lux-6", "146.66.158.0/23" ,
|
|
// "lux-7", "155.133.240.0/23" ,
|
|
// "lux-8", "155.133.240.0/23" ,
|
|
// "sgp-1", "103.28.54.0/23" ,
|
|
// "sgp-2", "103.28.54.0/23" ,
|
|
// "sgp-3", "103.28.54.0/23" ,
|
|
// "sto-1", "146.66.156.0/23" ,
|
|
// "sto-2", "146.66.156.0/23" ,
|
|
// "sto-3", "146.66.156.0/23" ,
|
|
// "sto-4", "185.25.180.0/23" ,
|
|
// "sto-5", "185.25.180.0/23" ,
|
|
// "sto-6", "185.25.180.0/23" ,
|
|
// "sto-7", "155.133.242.0/23" ,
|
|
// "sto-8", "155.133.242.0/23" ,
|
|
// "syd-1", "103.10.125.0/24",
|
|
// "vie-1", "146.66.155.0/24" ,
|
|
// "vie-2", "185.25.182.0/24" ,
|
|
// "jhb-1", "197.80.200.48/29" ,
|
|
"jhb-1", "155.133.238.0/24" ,
|
|
"jhb-2", "155.133.238.0/24" ,
|
|
// "cpt-1", "197.84.209.20/30",
|
|
// "bom-1", "45.113.191.128/27",
|
|
// "bom-1", "45.113.137.128/27",
|
|
// "tyo-1", "45.121.186.0/23",
|
|
// "tyo-2", "45.121.186.0/23",
|
|
// "hkg-1", "155.133.244.0/24",
|
|
// "mad-1", "155.133.246.0/23",
|
|
// "blv-1", "172.16.0.0/16",
|
|
// "scl-1", "155.133.249.0/24",
|
|
// "lim-1", "143.137.146.0/24",
|
|
// "ord-1", "155.133.226.0/24",
|
|
// "vie-3", "155.133.228.0/23",
|
|
// "syd-2", "155.133.227.0/24",
|
|
// "gru-5", "155.133.224.0/23",
|
|
// "jhb-2", "155.133.238.0/24",
|
|
// "bom-2", "155.133.233.0/24",
|
|
// "maa-1", "155.133.232.0/24",
|
|
// "waw-1", "155.133.230.0/23",
|
|
// "lim-2", "190.216.121.0/24",
|
|
// "atl-2", "155.133.234.0/24",
|
|
// "ord-1", "208.78.167.0/24",
|
|
// "ord-2", "208.78.167.0/24",
|
|
// "tsn-1", "125.39.181.0/24",
|
|
// "tsn-2", "60.28.165.128/25",
|
|
// "can-2", "125.88.174.0/24",
|
|
// "sha-3", "121.46.225.0/24",
|
|
// "eleague-major-atlanta-2017", "172.27.10.20/31",
|
|
};
|
|
|
|
for ( int j = 0; j < Q_ARRAYSIZE( arrAddressMasks )/2; ++j )
|
|
{
|
|
char const *szName = arrAddressMasks[ 2*j ];
|
|
char const *szAddr = arrAddressMasks[ 2*j+1 ];
|
|
char const *szSlash = strchr( szAddr, '/' );
|
|
if ( !szSlash ) continue;
|
|
|
|
char chBuffer[ 1024 ] = {};
|
|
V_sprintf_safe( chBuffer, "net_encrypt_key_generate %s %s.certificate;\n", args.Arg( 1 ), szName );
|
|
Cbuf_AddText( CBUF_FIRST_PLAYER, chBuffer );
|
|
|
|
V_sprintf_safe( chBuffer, "net_encrypt_key_signature \"%s\" %s.certificate.public \"%s\" %s.certificate.signature;",
|
|
args.Arg( 2 ), szName, szAddr, szName );
|
|
Cbuf_AddText( CBUF_FIRST_PLAYER, chBuffer );
|
|
|
|
V_sprintf_safe( chBuffer, "net_encrypt_key_compress %s.certificate;\n", szName );
|
|
Cbuf_AddText( CBUF_FIRST_PLAYER, chBuffer );
|
|
|
|
Cbuf_Execute();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|