//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// net_ws.c
// Windows IP Support layer.
# include "tier0/etwprof.h"
# include "tier0/vprof.h"
# include "net_ws_headers.h"
# include "net_ws_queued_packet_sender.h"
# include "fmtstr.h"
# include "master.h"
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
# define NET_COMPRESSION_STACKBUF_SIZE 4096
static ConVar net_showudp_wire ( " net_showudp_wire " , " 0 " , 0 , " Show incoming packet information " ) ;
# define UDP_SO_RCVBUF_SIZE 131072
static ConVar net_udp_rcvbuf ( " net_udp_rcvbuf " , NETSTRING ( UDP_SO_RCVBUF_SIZE ) , FCVAR_ALLOWED_IN_COMPETITIVE , " Default UDP receive buffer size " , true , 8192 , true , 128 * 1024 ) ;
static ConVar net_showsplits ( " net_showsplits " , " 0 " , 0 , " Show info about packet splits " ) ;
static ConVar net_splitrate ( " net_splitrate " , " 1 " , 0 , " Number of fragments for a splitpacket that can be sent per frame " ) ;
static ConVar ipname ( " ip " , " localhost " , FCVAR_ALLOWED_IN_COMPETITIVE , " Overrides IP for multihomed hosts " ) ;
static ConVar hostport ( " hostport " , NETSTRING ( PORT_SERVER ) , FCVAR_ALLOWED_IN_COMPETITIVE , " Host game server port " ) ;
static ConVar hostip ( " hostip " , " " , FCVAR_ALLOWED_IN_COMPETITIVE , " Host game server ip " ) ;
static ConVar clientport ( " clientport " , NETSTRING ( PORT_CLIENT ) , FCVAR_ALLOWED_IN_COMPETITIVE , " Host game client port " ) ;
static ConVar hltvport ( " tv_port " , NETSTRING ( PORT_HLTV ) , FCVAR_ALLOWED_IN_COMPETITIVE , " Host SourceTV port " ) ;
static ConVar matchmakingport ( " matchmakingport " , NETSTRING ( PORT_MATCHMAKING ) , FCVAR_ALLOWED_IN_COMPETITIVE , " Host Matchmaking port " ) ;
static ConVar systemlinkport ( " systemlinkport " , NETSTRING ( PORT_SYSTEMLINK ) , FCVAR_ALLOWED_IN_COMPETITIVE , " System Link port " ) ;
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 " , 0 , " Use network sockets layer even for listen server local player's packets (multiplayer only) . " ) ;
# 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
//-----------------------------------------------------------------------------
// Toggle Xbox 360 network security to allow cross-platform testing
//-----------------------------------------------------------------------------
# if !defined( _X360 )
# define X360SecureNetwork() false
# define IPPROTO_VDP IPPROTO_UDP
# elif defined( _RETAIL )
# 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_showtcp ;
extern ConVar net_blocksize ;
extern ConVar host_timescale ;
extern int host_framecount ;
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 ))
// For metering out splitpackets, don't do them too fast as remote UDP socket will drop some payloads causing them to always fail to be reconstituted
// This problem is largely solved by increasing the buffer sizes for UDP sockets on Windows
# define SPLITPACKET_MAX_DATA_BYTES_PER_SECOND V_STRINGIFY(DEFAULT_RATE)
static ConVar sv_maxroutable
(
" sv_maxroutable " ,
" 1260 " ,
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 " ,
" 1260 " ,
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 4 sockets, Server, Client, HLTV, Matchmaking
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
static bool net_notcp = true ; // Disable TCP support
static bool net_nohltv = false ; // disable HLTV support
static bool net_dedicated = false ; // true is dedicated system
static int net_error = 0 ; // global error code updated with NET_GetLastError()
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.
unsigned short NET_HostToNetShort ( unsigned short us_in )
{
return htons ( us_in ) ;
}
unsigned short NET_NetToHostShort ( unsigned short us_in )
{
return ntohs ( us_in ) ;
}
// This macro is used to capture the return value of a function call while recording
// a VCR file. During playback, it will get the return value out of the VCR file
// instead of actually calling the function.
# if !defined( NO_VCR )
# define VCR_NONPLAYBACKFN( call, resultVar, eventName ) \
{ \
if ( VCRGetMode ( ) ! = VCR_Playback ) \
resultVar = call ; \
\
VCRGenericValue ( eventName , & resultVar , sizeof ( resultVar ) ) ; \
}
# else
# define VCR_NONPLAYBACKFN( call, resultVar, eventName ) \
{ \
if ( VCRGetMode ( ) ! = VCR_Playback ) \
resultVar = call ; \
\
}
# endif
/*
= = = = = = = = = = = = = = = = = = = =
NET_ErrorString
= = = = = = = = = = = = = = = = = = = =
*/
const char * NET_ErrorString ( int code )
{
# if defined( _WIN32 )
switch ( code )
{
case WSAEINTR : return " WSAEINTR " ;
case WSAEBADF : return " WSAEBADF " ;
case WSAEACCES : return " WSAEACCES " ;
case WSAEDISCON : return " WSAEDISCON " ;
case WSAEFAULT : return " WSAEFAULT " ;
case WSAEINVAL : return " WSAEINVAL " ;
case WSAEMFILE : return " WSAEMFILE " ;
case WSAEWOULDBLOCK : return " WSAEWOULDBLOCK " ;
case WSAEINPROGRESS : return " WSAEINPROGRESS " ;
case WSAEALREADY : return " WSAEALREADY " ;
case WSAENOTSOCK : return " WSAENOTSOCK " ;
case WSAEDESTADDRREQ : return " WSAEDESTADDRREQ " ;
case WSAEMSGSIZE : return " WSAEMSGSIZE " ;
case WSAEPROTOTYPE : return " WSAEPROTOTYPE " ;
case WSAENOPROTOOPT : return " WSAENOPROTOOPT " ;
case WSAEPROTONOSUPPORT : return " WSAEPROTONOSUPPORT " ;
case WSAESOCKTNOSUPPORT : return " WSAESOCKTNOSUPPORT " ;
case WSAEOPNOTSUPP : return " WSAEOPNOTSUPP " ;
case WSAEPFNOSUPPORT : return " WSAEPFNOSUPPORT " ;
case WSAEAFNOSUPPORT : return " WSAEAFNOSUPPORT " ;
case WSAEADDRINUSE : return " WSAEADDRINUSE " ;
case WSAEADDRNOTAVAIL : return " WSAEADDRNOTAVAIL " ;
case WSAENETDOWN : return " WSAENETDOWN " ;
case WSAENETUNREACH : return " WSAENETUNREACH " ;
case WSAENETRESET : return " WSAENETRESET " ;
case WSAECONNABORTED : return " WSWSAECONNABORTEDAEINTR " ;
case WSAECONNRESET : return " WSAECONNRESET " ;
case WSAENOBUFS : return " WSAENOBUFS " ;
case WSAEISCONN : return " WSAEISCONN " ;
case WSAENOTCONN : return " WSAENOTCONN " ;
case WSAESHUTDOWN : return " WSAESHUTDOWN " ;
case WSAETOOMANYREFS : return " WSAETOOMANYREFS " ;
case WSAETIMEDOUT : return " WSAETIMEDOUT " ;
case WSAECONNREFUSED : return " WSAECONNREFUSED " ;
case WSAELOOP : return " WSAELOOP " ;
case WSAENAMETOOLONG : return " WSAENAMETOOLONG " ;
case WSAEHOSTDOWN : return " WSAEHOSTDOWN " ;
case WSASYSNOTREADY : return " WSASYSNOTREADY " ;
case WSAVERNOTSUPPORTED : return " WSAVERNOTSUPPORTED " ;
case WSANOTINITIALISED : return " WSANOTINITIALISED " ;
case WSAHOST_NOT_FOUND : return " WSAHOST_NOT_FOUND " ;
case WSATRY_AGAIN : return " WSATRY_AGAIN " ;
case WSANO_RECOVERY : return " WSANO_RECOVERY " ;
case WSANO_DATA : return " WSANO_DATA " ;
default : return " UNKNOWN ERROR " ;
}
# else
return strerror ( code ) ;
# endif
}
//-----------------------------------------------------------------------------
// 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 ;
}
void NET_ClearLastError ( void )
{
net_error = 0 ;
}
int NET_GetLastError ( void )
{
# if defined( _WIN32 )
net_error = WSAGetLastError ( ) ;
# else
net_error = errno ;
# endif
# if !defined( NO_VCR )
VCRGenericValue ( " WSAGetLastError " , & net_error , sizeof ( net_error ) ) ;
# 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 )
{
struct sockaddr saddr ;
char address [ 128 ] ;
Q_strncpy ( address , s , sizeof ( address ) ) ;
if ( ! Q_strncmp ( address , " localhost " , 10 ) | | ! Q_strncmp ( address , " localhost: " , 10 ) )
{
// subsitute 'localhost' with '127.0.0.1", both have 9 chars
// this way we can resolve 'localhost' without DNS and still keep the port
Q_memcpy ( address , " 127.0.0.1 " , 9 ) ;
}
if ( ! NET_StringToSockaddr ( address , & saddr ) )
return false ;
a - > SetFromSockadr ( & saddr ) ;
return true ;
}
CNetChan * NET_FindNetChannel ( int socket , netadr_t & 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 IP:Port address
if ( adr . CompareAdr ( 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
int ret ;
VCR_NONPLAYBACKFN ( closesocket ( hSocket ) , ret , " closesocket " ) ;
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 ;
}
}
}
/*
= = = = = = = = = = = = = = = = = = = =
NET_IPSocket
= = = = = = = = = = = = = = = = = = = =
*/
int NET_OpenSocket ( const char * net_interface , int & port , int protocol )
{
struct sockaddr_in address ;
unsigned int opt ;
int newsocket = - 1 ;
if ( protocol = = IPPROTO_TCP )
{
VCR_NONPLAYBACKFN ( socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ) , newsocket , " socket() " ) ;
}
else // as UDP or VDP
{
VCR_NONPLAYBACKFN ( socket ( PF_INET , SOCK_DGRAM , protocol ) , newsocket , " socket() " ) ;
}
if ( newsocket = = - 1 )
{
NET_GetLastError ( ) ;
if ( net_error ! = WSAEAFNOSUPPORT )
Msg ( " WARNING: NET_OpenSockett: socket failed: %s " , NET_ErrorString ( net_error ) ) ;
return 0 ;
}
opt = 1 ; // make it non-blocking
int ret ;
VCR_NONPLAYBACKFN ( ioctlsocket ( newsocket , FIONBIO , ( unsigned long * ) & opt ) , ret , " ioctlsocket " ) ;
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
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_KEEPALIVE , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
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 ;
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_LINGER , ( char * ) & optlinger , sizeof ( optlinger ) ) , ret , " setsockopt " ) ;
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.
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , IPPROTO_TCP , TCP_NODELAY , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
if ( ret = = - 1 )
{
NET_GetLastError ( ) ;
Msg ( " WARNING: NET_OpenSocket: setsockopt TCP_NODELAY: %s \n " , NET_ErrorString ( net_error ) ) ;
return 0 ;
}
opt = NET_MAX_MESSAGE ; // set TCP options: set send buffer size
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_SNDBUF , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
if ( ret = = - 1 )
{
NET_GetLastError ( ) ;
Msg ( " WARNING: NET_OpenSocket: setsockopt SO_SNDBUF: %s \n " , NET_ErrorString ( net_error ) ) ;
return 0 ;
}
opt = NET_MAX_MESSAGE ; // set TCP options: set receive buffer size
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_RCVBUF , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
if ( ret = = - 1 )
{
NET_GetLastError ( ) ;
Msg ( " WARNING: NET_OpenSocket: setsockopt SO_RCVBUF: %s \n " , NET_ErrorString ( net_error ) ) ;
return 0 ;
}
return newsocket ; // don't bind TCP sockets by default
}
// rest is UDP only
opt = 0 ;
socklen_t len = sizeof ( opt ) ;
VCR_NONPLAYBACKFN ( getsockopt ( newsocket , SOL_SOCKET , SO_RCVBUF , ( char * ) & opt , & len ) , ret , " getsockopt " ) ;
if ( ret = = - 1 )
{
NET_GetLastError ( ) ;
Msg ( " WARNING: NET_OpenSocket: getsockopt SO_RCVBUF: %s \n " , NET_ErrorString ( net_error ) ) ;
return 0 ;
}
if ( net_showudp . GetBool ( ) )
{
static bool bFirst = true ;
if ( bFirst )
{
Msg ( " UDP socket SO_RCVBUF size %d bytes, changing to %d \n " , opt , net_udp_rcvbuf . GetInt ( ) ) ;
}
bFirst = false ;
}
opt = net_udp_rcvbuf . GetInt ( ) ; // set UDP receive buffer size
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_RCVBUF , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
if ( ret = = - 1 )
{
NET_GetLastError ( ) ;
Msg ( " WARNING: NET_OpenSocket: setsockopt SO_RCVBUF: %s \n " , NET_ErrorString ( net_error ) ) ;
return 0 ;
}
opt = net_udp_rcvbuf . GetInt ( ) ; // set UDP send buffer size
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_SNDBUF , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
if ( ret = = - 1 )
{
NET_GetLastError ( ) ;
Msg ( " WARNING: NET_OpenSocket: setsockopt SO_SNDBUF: %s \n " , NET_ErrorString ( net_error ) ) ;
return 0 ;
}
// VDP protocol (Xbox 360 secure network) doesn't support SO_BROADCAST
if ( ! X360SecureNetwork ( ) | | protocol ! = IPPROTO_VDP )
{
opt = 1 ; // set UDP options: make it broadcast capable
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_BROADCAST , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
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
VCR_NONPLAYBACKFN ( setsockopt ( newsocket , SOL_SOCKET , SO_REUSEADDR , ( char * ) & opt , sizeof ( opt ) ) , ret , " setsockopt " ) ;
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
for ( port_offset = 0 ; port_offset < PORT_TRY_MAX ; port_offset + + )
{
if ( port = = PORT_ANY )
{
address . sin_port = 0 ; // = INADDR_ANY
}
else
{
address . sin_port = NET_HostToNetShort ( ( short ) ( port + port_offset ) ) ;
}
VCR_NONPLAYBACKFN ( bind ( newsocket , ( struct sockaddr * ) & address , sizeof ( address ) ) , ret , " bind " ) ;
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: NNET_OpenSocket: bind: %s \n " , NET_ErrorString ( net_error ) ) ;
NET_CloseSocket ( newsocket , - 1 ) ;
return 0 ;
}
// Try next port
}
const bool bStrictBind = CommandLine ( ) - > FindParm ( " -strictportbind " ) ;
if ( port_offset = = PORT_TRY_MAX & & ! bStrictBind )
{
Msg ( " WARNING: UDP_OpenSocket: unable to bind socket \n " ) ;
NET_CloseSocket ( newsocket , - 1 ) ;
return 0 ;
}
if ( port_offset > 0 )
{
if ( bStrictBind )
{
// The server op wants to exit if the desired port was not avialable.
Sys_Exit ( " ERROR: Port %i was unavailable - quitting due to \" -strictportbind \" command-line flag! \n " , port - port_offset ) ;
}
else
{
Warning ( " WARNING: Port %i was unavailable - bound to port %i instead \n " , port - port_offset , port ) ;
}
}
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 ;
VCR_NONPLAYBACKFN ( connect ( netsock - > hTCP , & saddr , sizeof ( saddr ) ) , ret , " connect " ) ;
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 )
{
//int ret = send( nSock, buf, len, flags );
int ret = VCRHook_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 )
{
int ret = VCRHook_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 , netadr_t * adr , const char * name , INetChannelHandler * handler , bool bForceNewChannel /*=false*/ ,
int nProtocolVersion /*=PROTOCOL_VERSION*/ )
{
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
chan - > Setup ( socket , adr , name , handler , nProtocolVersion ) ;
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 ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
LOOPBACK BUFFERS FOR LOCAL PLAYER
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
void NET_SendLoopPacket ( int sock , int length , const unsigned char * data , const netadr_t & to )
{
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 )
{
static double s_LastTime = 0 ;
// Bound time step
float dt = net_time - s_LastTime ;
dt = clamp ( dt , 0.0f , 0.2f ) ;
s_LastTime = net_time ;
// Already converged?
if ( fakelag . GetFloat ( ) = = s_FakeLag )
return ;
// 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 ;
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 2.0f
# define SPLIT_PACKET_TRACKING_MAX 256 // most number of outstanding split packets to allow
class CSplitPacketEntry
{
public :
CSplitPacketEntry ( )
{
memset ( & from , 0 , sizeof ( from ) ) ;
int i ;
for ( i = 0 ; i < MAX_SPLITPACKET_SPLITS ; i + + )
{
splitflags [ i ] = - 1 ;
}
memset ( & netsplit , 0 , sizeof ( netsplit ) ) ;
lastactivetime = 0.0f ;
}
public :
netadr_t 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 )
{
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 ) ;
}
if ( splitPacketEntries . Count ( ) > SPLIT_PACKET_TRACKING_MAX )
{
while ( splitPacketEntries . Count ( ) > SPLIT_PACKET_TRACKING_MAX )
{
CSplitPacketEntry * entry = & splitPacketEntries [ i ] ;
if ( net_time ! = entry - > lastactivetime )
splitPacketEntries . Remove ( 0 ) ; // we add to tail each time, so head is the oldest entry, kill them first
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *from -
// Output : CSplitPacketEntry
//-----------------------------------------------------------------------------
CSplitPacketEntry * NET_FindOrCreateSplitPacketEntry ( const int sock , netadr_t * 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 - > CompareAdr ( entry - > from ) )
break ;
}
if ( i > = count )
{
CSplitPacketEntry newentry ;
newentry . from = * from ;
splitPacketEntries . AddToTail ( newentry ) ;
entry = & splitPacketEntries [ splitPacketEntries . Count ( ) - 1 ] ;
}
Assert ( entry ) ;
return entry ;
}
static char const * DescribeSocket ( int sock )
{
switch ( sock )
{
default :
break ;
case NS_CLIENT :
return " cl " ;
case NS_SERVER :
return " sv " ;
case NS_HLTV :
return " htv " ;
case NS_MATCHMAKING :
return " mat " ;
case NS_SYSTEMLINK :
return " lnk " ;
# ifdef LINUX
case NS_SVLAN :
return " lan " ;
# endif
}
return " ?? " ;
}
//-----------------------------------------------------------------------------
// 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 ;
}
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 [%llu - %llu] \n " ,
packet - > from . ToString ( ) ,
packetNumber ,
packetCount ,
nSplitSizeMinusHeader ,
( uint64 ) MIN_SPLIT_SIZE ,
( uint64 ) 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 %llu is max count allowed \n " ,
packet - > from . ToString ( ) ,
packetNumber ,
packetCount ,
( uint64 ) MAX_SPLITPACKET_SPLITS ) ;
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 ) ) ;
// 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 " ,
packet - > from . ToString ( ) ,
packetNumber ,
packetCount ,
nSplitSizeMinusHeader ,
entry - > netsplit . nExpectedSplitSize
) ;
entry - > lastactivetime = net_time + SPLIT_PACKET_STALE_TIME ;
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 %4llu from %s \n " ,
DescribeSocket ( sock ) ,
packetNumber + 1 ,
packetCount ,
sequenceNumber ,
size ,
( uint64 ) ( nSplitSizeMinusHeader + sizeof ( SPLITPACKET ) ) ,
packet - > from . ToString ( ) ) ;
}
}
else
{
Msg ( " NET_GetLong: Ignoring duplicated split packet %i of %i ( %i bytes ) from %s \n " , packetNumber + 1 , packetCount , size , packet - > from . ToString ( ) ) ;
}
// 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 , packet - > from . ToString ( ) ) ;
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 ;
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 . 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 ) ) ;
}
bool NET_ReceiveDatagram ( const int sock , netpacket_t * packet )
{
VPROF_BUDGET ( " NET_ReceiveDatagram " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
Assert ( packet ) ;
Assert ( net_multiplayer ) ;
struct sockaddr from ;
int fromlen = sizeof ( from ) ;
int net_socket = net_sockets [ packet - > source ] . hUDP ;
int ret = 0 ;
{
VPROF_BUDGET ( " recvfrom " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
ret = VCRHook_recvfrom ( net_socket , ( char * ) packet - > data , NET_MAX_MESSAGE , 0 , ( struct sockaddr * ) & from , ( int * ) & fromlen ) ;
}
if ( ret > = NET_MIN_MESSAGE )
{
packet - > wiresize = ret ;
packet - > from . SetFromSockadr ( & from ) ;
packet - > size = ret ;
if ( net_showudp_wire . GetBool ( ) )
{
Msg ( " WIRE: UDP sz=%d tm=%f rt %f from %s \n " , ret , net_time , Plat_FloatTime ( ) , packet - > from . ToString ( ) ) ;
}
MEM_ALLOC_CREDIT ( ) ;
CUtlMemoryFixedGrowable < byte , NET_COMPRESSION_STACKBUF_SIZE > bufVoice ( NET_COMPRESSION_STACKBUF_SIZE ) ;
unsigned int nVoiceBits = 0u ;
if ( X360SecureNetwork ( ) )
{
// 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 ) ;
Assert ( nDataBytes > 0 & & nDataBytes < = ret ) ;
int nVoiceBytes = ret - nDataBytes - 2 ;
if ( nVoiceBytes > 0 )
{
char * pVoice = ( char * ) packet - > data + 2 + nDataBytes ;
nVoiceBits = ( unsigned int ) LittleShort ( * ( unsigned short * ) pVoice ) ;
unsigned int nExpectedVoiceBytes = Bits2Bytes ( nVoiceBits ) ;
pVoice + = sizeof ( unsigned short ) ;
int nCompressedSize = nVoiceBytes - sizeof ( unsigned short ) ;
int nDecompressedVoice = COM_GetUncompressedSize ( pVoice , nCompressedSize ) ;
if ( nDecompressedVoice > = 0 )
{
if ( ( unsigned ) nDecompressedVoice ! = nExpectedVoiceBytes )
{
return false ;
}
bufVoice . EnsureCapacity ( nDecompressedVoice ) ;
// Decompress it
unsigned unActualDecompressedSize = ( unsigned ) nDecompressedVoice ;
if ( ! COM_BufferToBufferDecompress ( ( char * ) bufVoice . Base ( ) , & unActualDecompressedSize , pVoice , nCompressedSize ) )
return false ;
Assert ( unActualDecompressedSize = = ( unsigned ) nDecompressedVoice ) ;
nVoiceBytes = unActualDecompressedSize ;
}
else
{
bufVoice . EnsureCapacity ( nVoiceBytes ) ;
Q_memcpy ( bufVoice . Base ( ) , pVoice , nVoiceBytes ) ;
}
}
Q_memmove ( packet - > data , & packet - > data [ 2 ] , nDataBytes ) ;
ret = nDataBytes ;
}
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 ;
}
// Next check for compressed message
if ( LittleLong ( * ( int * ) packet - > data ) = = NET_HEADER_FLAG_COMPRESSEDPACKET )
{
char * pCompressedData = ( char * ) packet - > data + sizeof ( unsigned int ) ;
unsigned nCompressedDataSize = packet - > wiresize - sizeof ( unsigned int ) ;
// Decompress
int actualSize = COM_GetUncompressedSize ( pCompressedData , nCompressedDataSize ) ;
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 uDecompressedSize = ( unsigned ) actualSize ;
COM_BufferToBufferDecompress ( ( char * ) memDecompressed . Base ( ) , & uDecompressedSize , pCompressedData , nCompressedDataSize ) ;
if ( uDecompressedSize = = 0 | | ( ( unsigned int ) actualSize ) ! = uDecompressedSize )
{
if ( net_showudp . GetBool ( ) )
{
Msg ( " UDP: discarding %d bytes from %s due to decompression error [%d decomp, actual %d] at tm=%f rt=%f \n " , ret , packet - > from . ToString ( ) , uDecompressedSize , actualSize ,
( float ) net_time , ( float ) Plat_FloatTime ( ) ) ;
}
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 ;
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 ) )
{
fixup . WriteUBitLong ( net_NOP , NETMSG_TYPE_BITS ) ;
}
packet - > size = fixup . GetNumBytesWritten ( ) ;
}
return NET_LagPacket ( true , packet ) ;
}
else
{
ConDMsg ( " NET_ReceiveDatagram: Oversize packet from %s \n " , packet - > from . ToString ( ) ) ;
}
}
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 ;
}
bool NET_ReceiveValidDatagram ( const int sock , netpacket_t * packet )
{
# ifdef _DEBUG
if ( recvpackets . GetInt ( ) > = 0 )
{
unsigned long bytes = 0 ;
ioctlsocket ( net_sockets [ sock ] . hUDP , FIONREAD , & bytes ) ;
if ( bytes < = 0 )
return false ;
if ( recvpackets . GetInt ( ) = = 0 )
return false ;
recvpackets . SetValue ( recvpackets . GetInt ( ) - 1 ) ;
}
# endif
// Failsafe: never call recvfrom more than a fixed number of times per frame.
// We don't like the potential for infinite loops. Yes this means that 66000
// invalid packets per frame will effectively DOS the server, but at that point
// you're basically flooding the network and you need to solve this at a higher
// firewall or router level instead which is beyond the scope of our netcode.
// --henryg 10/12/2011
for ( int i = 1000 ; i > 0 ; - - i )
{
// Attempt to receive a valid packet.
NET_ClearLastError ( ) ;
if ( NET_ReceiveDatagram ( sock , packet ) )
{
// Received a valid packet.
return true ;
}
// NET_ReceiveDatagram calls Net_GetLastError() in case of socket errors
// or a would-have-blocked-because-there-is-no-data-to-read condition.
if ( net_error )
{
break ;
}
}
return false ;
}
netpacket_t * NET_GetPacket ( int sock , byte * scratch )
{
VPROF_BUDGET ( " NET_GetPacket " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
// 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 . SetType ( NA_IP ) ;
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 ) )
{
if ( ! NET_IsMultiplayer ( ) & & sock ! = NS_CLIENT )
{
return NULL ;
}
// then check UDP data
if ( ! NET_ReceiveValidDatagram ( 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 , 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 )
return ;
sockaddr sa ;
int nLengthAddr = sizeof ( sa ) ;
int newSocket ;
VCR_NONPLAYBACKFN ( accept ( netsock - > hTCP , & sa , ( socklen_t * ) & nLengthAddr ) , newSocket , " accept " ) ;
# if !defined( NO_VCR )
VCRGenericValue ( " sockaddr " , & sa , sizeof ( sa ) ) ;
# endif
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 ( ) ) ;
}
}
struct NetScratchBuffer_t : TSLNodeBase_t
{
byte data [ NET_MAX_MESSAGE ] ;
} ;
CTSSimpleList < NetScratchBuffer_t > g_NetScratchBuffers ;
void NET_ProcessSocket ( int sock , IConnectionlessPacketHandler * handler )
{
VPROF_BUDGET ( " NET_ProcessSocket " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
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
NetScratchBuffer_t * scratch = g_NetScratchBuffers . Pop ( ) ;
if ( ! scratch )
{
scratch = new NetScratchBuffer_t ;
}
while ( ( packet = NET_GetPacket ( sock , scratch - > data ) ) ! = 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 ( ) )
{
Msg ( " UDP <- %s: sz=%i OOB '%c' wire=%i \n " , packet - > from . ToString ( ) , packet - > size , packet - > data [ 4 ] , packet - > wiresize ) ;
}
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 " , packet - > from . ToString ( ) ) ;
} */
}
g_NetScratchBuffers . Push ( scratch ) ;
}
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 " , packet - > from . ToString ( ) , filename ) ;
}
else
{
Msg ( " Couldn't write error buffer, delete error###.dat files to make space \n " ) ;
}
}
int NET_SendToImpl ( SOCKET s , const char FAR * buf , int len , const struct sockaddr FAR * to , int tolen , int iGameDataLength )
{
int nSend = 0 ;
# if defined( _X360 )
if ( X360SecureNetwork ( ) )
{
// 360 uses VDP protocol to piggyback voice data across the network.
// Two-byte VDP Header contains the number of game data bytes
// NOTE: The header bytes *should* be swapped to network endian, however when communicating
// with XLSP servers (the only cross-platform communication possible with a secure network)
// the server's network stack swaps the header at the receiving end.
const int nVDPHeaderBytes = 2 ;
Assert ( len < ( unsigned short ) - 1 ) ;
const unsigned short nDataBytes = iGameDataLength = = - 1 ? len : iGameDataLength ;
WSABUF buffers [ 2 ] ;
buffers [ 0 ] . len = nVDPHeaderBytes ;
buffers [ 0 ] . buf = ( char * ) & nDataBytes ;
buffers [ 1 ] . len = len ;
buffers [ 1 ] . buf = const_cast < char * > ( buf ) ;
WSASendTo ( s , buffers , 2 , ( DWORD * ) & nSend , 0 , to , tolen , NULL , NULL ) ;
}
else
# endif //defined( _X360 )
{
nSend = sendto ( s , buf , len , 0 , to , tolen ) ;
}
return nSend ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : sock -
// s -
// buf -
// len -
// flags -
// to -
// tolen -
// Output : int
//-----------------------------------------------------------------------------
bool CL_IsHL2Demo ( ) ;
bool CL_IsPortalDemo ( ) ;
int NET_SendTo ( bool verbose , SOCKET s , const char FAR * buf , int len , const struct sockaddr FAR * to , int tolen , int iGameDataLength )
{
int nSend = 0 ;
VPROF_BUDGET ( " NET_SendTo " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
// 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.
sockaddr_in * pInternetAddr = ( sockaddr_in * ) to ;
# ifdef _WIN32
if ( pInternetAddr - > sin_addr . S_un . S_addr = = 0
# else
if ( pInternetAddr - > sin_addr . s_addr = = 0
# endif
& & pInternetAddr - > sin_port = = 0 )
{
return len ;
}
// Normally, we shouldn't need to write this data to the file, but it can help catch
// out-of-sync errors earlier.
if ( VCRGetMode ( ) ! = VCR_Disabled & & vcr_verbose . GetInt ( ) )
{
# if !defined( NO_VCR )
VCRGenericValue ( " senddata " , & len , sizeof ( len ) ) ;
VCRGenericValue ( " senddata2 " , ( char * ) buf , len ) ;
# endif
}
// Don't send anything out in VCR mode.. it just annoys other people testing in multiplayer.
if ( VCRGetMode ( ) ! = VCR_Playback )
{
# ifndef SWDS
if ( ( CL_IsHL2Demo ( ) | | CL_IsPortalDemo ( ) ) & & ! net_dedicated )
{
Error ( " " ) ;
}
# endif // _WIN32
nSend = NET_SendToImpl
(
s ,
buf ,
len ,
to ,
tolen ,
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 ;
CUtlBuffer m_To ;
} ;
struct SendQueue_t
{
SendQueue_t ( ) :
m_nHostFrame ( 0 )
{
}
int m_nHostFrame ;
CUtlLinkedList < SendQueueItem_t > m_SendQueue ;
} ;
static SendQueue_t g_SendQueue ;
int NET_QueuePacketForSend ( CNetChan * chan , bool verbose , SOCKET s , const char FAR * buf , int len , const struct sockaddr FAR * to , int tolen , 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 , tolen , 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 . Put ( ( const void * ) to , tolen ) ;
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 ( ) ,
( const struct sockaddr FAR * ) sq - > m_To . Base ( ) ,
sq - > m_To . TellPut ( ) , - 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 ConVar net_splitpacket_maxrate ( " net_splitpacket_maxrate " , SPLITPACKET_MAX_DATA_BYTES_PER_SECOND , 0 , " Max bytes per second when queueing splitpacket chunks " , true , MIN_RATE , true , MAX_RATE ) ;
int NET_SendLong ( INetChannel * chan , int sock , SOCKET s , const char FAR * buf , int len , const struct sockaddr FAR * to , int tolen , int nMaxRoutableSize )
{
VPROF_BUDGET ( " NET_SendLong " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
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 ( ( int ) 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 (SPLITPACKET_MAX_DATA_BYTES_PER_SECOND == 15000 bytes/sec)
// or user's won't be able to receive all of the parts since they'll be too close together.
/// XXX(JohnS): (float)cv.GetInt() is just preserving what this was doing before to avoid changing the
/// semantics of this convar
float flMaxSplitpacketDataRateBytesPerSecond = min ( ( float ) netchan - > GetDataRate ( ) , ( float ) net_splitpacket_maxrate . GetInt ( ) ) ;
// 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 , tolen , 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 , tolen , - 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 )
{
netadr_t adr ;
adr . SetFromSockadr ( ( struct sockaddr * ) to ) ;
Msg ( " --> [%s] Split packet %4i/%4i seq %5i size %4i mtu %4i to %s [ total %4i ] \n " ,
DescribeSocket ( sock ) ,
nPacketNumber ,
nPacketCount ,
nSequenceNumber ,
size ,
nMaxRoutableSize ,
adr . ToString ( ) ,
sendlen ) ;
}
}
return nTotalBytesSent ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : sock -
// length -
// *data -
// to -
// Output : void NET_SendPacket
//-----------------------------------------------------------------------------
int NET_SendPacket ( INetChannel * chan , int sock , const netadr_t & to , const unsigned char * data , int length , bf_write * pVoicePayload /* = NULL */ , bool bUseCompression /*=false*/ )
{
VPROF_BUDGET ( " NET_SendPacket " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
ETWSendPacket ( to . ToString ( ) , length , 0 , 0 ) ;
int ret ;
struct sockaddr addr ;
int net_socket ;
if ( net_showudp . GetInt ( ) & & ( * ( unsigned int * ) data = = CONNECTIONLESS_HEADER ) )
{
Assert ( ! bUseCompression ) ;
Msg ( " UDP -> %s: sz=%i OOB '%c' \n " , to . ToString ( ) , length , data [ 4 ] ) ;
}
if ( ( ! NET_IsMultiplayer ( ) & & sock ! = NS_CLIENT ) | | to . type = = NA_LOOPBACK | | ( to . IsLocalhost ( ) & & ! net_usesocketsforloopback . GetBool ( ) ) )
{
Assert ( ! pVoicePayload ) ;
NET_SendLoopPacket ( sock , length , data , to ) ;
return length ;
}
if ( to . type = = NA_BROADCAST )
{
net_socket = net_sockets [ sock ] . hUDP ;
if ( ! net_socket )
return length ;
}
else if ( to . type = = NA_IP )
{
net_socket = net_sockets [ sock ] . hUDP ;
if ( ! net_socket )
return length ;
}
else
{
DevMsg ( " NET_SendPacket: bad address type (%i) \n " , to . type ) ;
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 ;
}
to . ToSockadr ( & addr ) ;
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 ) ;
int iGameDataLength = pVoicePayload ? length : - 1 ;
bool bWroteVoice = false ;
unsigned int nVoiceBytes = 0 ;
if ( pVoicePayload )
{
VPROF_BUDGET ( " NET_SendPacket_CompressVoice " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
unsigned int nCompressedLength = COM_GetIdealDestinationCompressionBufferSize_Snappy ( pVoicePayload - > GetNumBytesWritten ( ) ) ;
memCompressedVoice . EnsureCapacity ( nCompressedLength + sizeof ( unsigned short ) ) ;
byte * pVoice = ( byte * ) memCompressedVoice . Base ( ) ;
unsigned short usVoiceBits = pVoicePayload - > GetNumBitsWritten ( ) ;
* ( unsigned short * ) pVoice = LittleShort ( usVoiceBits ) ;
pVoice + = sizeof ( unsigned short ) ;
byte * pOutput = NULL ;
if ( net_compressvoice . GetBool ( ) )
{
if ( COM_BufferToBufferCompress_Snappy ( pVoice , & nCompressedLength , pVoicePayload - > GetData ( ) , pVoicePayload - > GetNumBytesWritten ( )
& & ( ( int ) nCompressedLength < pVoicePayload - > GetNumBytesWritten ( ) ) ) )
{
pOutput = pVoice ;
}
}
if ( ! pOutput )
{
Q_memcpy ( pVoice , pVoicePayload - > GetData ( ) , pVoicePayload - > GetNumBytesWritten ( ) ) ;
}
nVoiceBytes = nCompressedLength + sizeof ( unsigned short ) ;
}
if ( bUseCompression )
{
VPROF_BUDGET ( " NET_SendPacket_Compress " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
unsigned int nCompressedLength = COM_GetIdealDestinationCompressionBufferSize_Snappy ( length ) ;
memCompressed . EnsureCapacity ( nCompressedLength + nVoiceBytes + sizeof ( unsigned int ) ) ;
* ( int * ) memCompressed . Base ( ) = LittleLong ( NET_HEADER_FLAG_COMPRESSEDPACKET ) ;
if ( COM_BufferToBufferCompress_Snappy ( memCompressed . Base ( ) + sizeof ( unsigned int ) , & nCompressedLength , data , length )
& & ( int ) nCompressedLength < length )
{
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 ;
}
// 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 ( 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 , & addr , sizeof ( addr ) , iGameDataLength ) ;
}
else
{
// split packet into smaller pieces
ret = NET_SendLong ( chan , sock , net_socket , ( const char * ) data , length , & addr , sizeof ( addr ) , 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 . type = = NA_BROADCAST ) )
return 0 ;
ConDMsg ( " NET_SendPacket Warning: %s : %s \n " , NET_ErrorString ( net_error ) , to . ToString ( ) ) ;
ret = length ;
}
return ret ;
}
void NET_OutOfBandPrintf ( int sock , const netadr_t & 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 ) ;
}
/*
= = = = = = = = = = = = = = = = = = = =
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 ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = =
NET_FlushAllSockets
= = = = = = = = = = = = = = = = = = = =
*/
void NET_FlushAllSockets ( void )
{
// drain any packets that my still lurk in our incoming queue
char data [ 2048 ] ;
struct sockaddr from ;
int fromlen = sizeof ( 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 = VCRHook_recvfrom ( net_sockets [ i ] . hUDP , data , sizeof ( data ) , 0 , ( struct sockaddr * ) & from , ( int * ) & fromlen ) ;
}
}
}
}
enum
{
OSOCKET_FLAG_USE_IPNAME = 0x00000001 , // Use ipname convar for net_interface.
OSOCKET_FLAG_FAIL = 0x00000002 , // Call Sys_exit on error.
} ;
static bool OpenSocketInternal ( int nModule , int nSetPort , int nDefaultPort , const char * pName , int nProtocol , bool bTryAny ,
int flags = ( OSOCKET_FLAG_USE_IPNAME | OSOCKET_FLAG_FAIL ) )
{
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 false ;
}
if ( ! net_sockets [ nModule ] . nPort )
{
const char * netinterface = ( flags & OSOCKET_FLAG_USE_IPNAME ) ? ipname . GetString ( ) : NULL ;
* handle = NET_OpenSocket ( netinterface , port , nProtocol ) ;
if ( ! * handle & & bTryAny )
{
port = PORT_ANY ; // try again with PORT_ANY
* handle = NET_OpenSocket ( netinterface , port , nProtocol ) ;
}
if ( ! * handle )
{
if ( flags & OSOCKET_FLAG_FAIL )
Sys_Exit ( " Couldn't allocate any %s IP port " , pName ) ;
return false ;
}
net_sockets [ nModule ] . nPort = port ;
}
else
{
Msg ( " WARNING: NET_OpenSockets: %s port %i already open. \n " , pName , net_sockets [ nModule ] . nPort ) ;
return false ;
}
return ( net_sockets [ nModule ] . nPort ! = 0 ) ;
}
/*
= = = = = = = = = = = = = = = = = = = =
NET_OpenSockets
= = = = = = = = = = = = = = = = = = = =
*/
void NET_OpenSockets ( void )
{
// Xbox 360 uses VDP protocol to combine encrypted game data with clear voice data
const int nProtocol = X360SecureNetwork ( ) ? IPPROTO_VDP : IPPROTO_UDP ;
OpenSocketInternal ( NS_SERVER , hostport . GetInt ( ) , PORT_SERVER , " server " , nProtocol , false ) ;
OpenSocketInternal ( NS_CLIENT , clientport . GetInt ( ) , PORT_SERVER , " client " , nProtocol , true ) ;
if ( ! net_nohltv )
{
OpenSocketInternal ( NS_HLTV , hltvport . GetInt ( ) , PORT_HLTV , " hltv " , nProtocol , false ) ;
}
if ( IsX360 ( ) )
{
OpenSocketInternal ( NS_MATCHMAKING , matchmakingport . GetInt ( ) , PORT_MATCHMAKING , " matchmaking " , nProtocol , false ) ;
OpenSocketInternal ( NS_SYSTEMLINK , systemlinkport . GetInt ( ) , PORT_SYSTEMLINK , " systemlink " , IPPROTO_UDP , false ) ;
}
# ifdef LINUX
// On Linux, if you bind to a specific address then you will NOT receive broadcast messages.
// This means that if you do a +ip X.X.X.X, your game will not show up on the LAN server browser page.
// To workaround this, if the user has specified sv_lan and an IP address, we open an INADDR_ANY port.
// See http://developerweb.net/viewtopic.php?id=5722 for more information.
extern ConVar sv_lan ;
if ( sv_lan . GetBool ( ) )
{
const char * net_interface = ipname . GetString ( ) ;
// If net_interface was specified and it's not localhost...
if ( net_interface [ 0 ] & & ( Q_strcmp ( net_interface , " localhost " ) ! = 0 ) )
{
// From clientdll/matchmaking/ServerList.cpp, the ports queried are:
// 27015 - 27020, 26900 - 26905
// 4242: RDKF, 27215: Lost Planet
static int s_ports [ ] =
{
26900 , 26901 , 26902 , 26903 , 26904 , 26905 ,
27015 , 27016 , 27017 , 27018 , 27019 , 27020
} ;
for ( size_t iport = 0 ; iport < ARRAYSIZE ( s_ports ) ; iport + + )
{
bool bPortUsed = false ;
for ( int i = NS_CLIENT ; i < NS_SVLAN ; i + + )
{
// Move along if this port is already used.
if ( net_sockets [ i ] . nPort = = s_ports [ iport ] )
{
bPortUsed = true ;
break ;
}
}
if ( ! bPortUsed )
{
// Try to open the socket and break if we succeeded.
if ( OpenSocketInternal ( NS_SVLAN , s_ports [ iport ] , PORT_SERVER , " lan " , nProtocol , false , 0 ) )
break ;
}
}
if ( net_sockets [ NS_SVLAN ] . nPort )
Msg ( " Opened sv_lan port %d \n " , net_sockets [ NS_SVLAN ] . nPort ) ;
else
Warning ( " %s, Failed to open sv_lan port. \n " , __FUNCTION__ ) ;
}
}
# endif // LINUX
}
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 )
{
if ( socket < 0 | | socket > = net_sockets . Count ( ) )
return 0 ;
return net_sockets [ socket ] . nPort ;
}
/*
= = = = = = = = = = = = = = = =
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
{
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
{
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 ) ;
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 _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 flRealtime )
{
static double s_last_realtime = 0 ;
double frametime = flRealtime - s_last_realtime ;
s_last_realtime = flRealtime ;
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 ;
}
// adjust network time so fakelag works with host_timescale
net_time + = frametime * host_timescale . GetFloat ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = =
NET_RunFrame
RunFrame must be called each system frame before reading / sending on any socket
= = = = = = = = = = = = = = = = = = = =
*/
void NET_RunFrame ( double flRealtime )
{
NET_SetTime ( flRealtime ) ;
RCONServer ( ) . RunFrame ( ) ;
# ifdef ENABLE_RPT
RPTServer ( ) . RunFrame ( ) ;
# endif // ENABLE_RPT
# ifndef SWDS
RCONClient ( ) . RunFrame ( ) ;
# ifdef ENABLE_RPT
RPTClient ( ) . RunFrame ( ) ;
# endif // ENABLE_RPT
# endif // SWDS
master - > RunFrame ( ) ;
# ifdef _X360
if ( net_logserver . GetInt ( ) )
{
NET_LogServerStatus ( ) ;
}
g_pMatchmaking - > RunFrame ( ) ;
# endif
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 ;
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 reconfiguare
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 ) ;
}
Msg ( " Network: IP %s, mode %s, dedicated %s, ports %i SV / %i CL \n " ,
net_local_adr . ToString ( true ) , 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 ;
}
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 ( ) & & sock ! = NS_CLIENT ) | | 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 ;
VCR_NONPLAYBACKFN ( bind ( netsock - > hTCP , ( struct sockaddr * ) & address , sizeof ( address ) ) , ret , " bind " ) ;
if ( ret = = - 1 )
{
NET_GetLastError ( ) ;
Msg ( " WARNING: NET_ListenSocket bind failed on socket %i, port %i. \n " , netsock - > hTCP , netsock - > nPort ) ;
return ;
}
VCR_NONPLAYBACKFN ( listen ( netsock - > hTCP , TCP_MAX_ACCEPTS ) , ret , " listen " ) ;
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_SetMutiplayer ( 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 ( ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bIsDedicated -
//-----------------------------------------------------------------------------
void NET_Init ( bool bIsDedicated )
{
if ( CommandLine ( ) - > FindParm ( " -NoQueuedPacketThread " ) )
Warning ( " Found -NoQueuedPacketThread, so no queued packet thread will be created. \n " ) ;
else
g_pQueuedPackedSender - > Setup ( ) ;
if ( CommandLine ( ) - > FindParm ( " -nodns " ) )
{
net_nodns = true ;
}
if ( CommandLine ( ) - > FindParm ( " -usetcp " ) )
{
net_notcp = false ;
}
if ( CommandLine ( ) - > FindParm ( " -nohltv " ) )
{
net_nohltv = true ;
}
if ( CommandLine ( ) - > FindParm ( " -noip " ) )
{
net_noip = true ;
}
else
{
# if defined(_WIN32)
# if defined(_X360)
XNetStartupParams xnsp ;
memset ( & xnsp , 0 , sizeof ( xnsp ) ) ;
xnsp . cfgSizeOfStruct = sizeof ( XNetStartupParams ) ;
if ( X360SecureNetwork ( ) )
{
Msg ( " Xbox 360 network is Secure \n " ) ;
}
else
{
// Allow cross-platform communication
xnsp . cfgFlags = XNET_STARTUP_BYPASS_SECURITY ;
Msg ( " Xbox 360 network is Unsecure \n " ) ;
}
INT err = XNetStartup ( & xnsp ) ;
if ( err )
{
ConMsg ( " Error! Failed to set XNET Security Bypass. \n " ) ;
}
err = XOnlineStartup ( ) ;
if ( err ! = ERROR_SUCCESS )
{
ConMsg ( " Error! XOnlineStartup failed. \n " ) ;
}
# else
// 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 ;
}
# endif // _X360
# endif // _WIN32
}
COMPILE_TIME_ASSERT ( SVC_LASTMSG < ( 1 < < NETMSG_TYPE_BITS ) ) ;
COMPILE_TIME_ASSERT ( MAX_FILE_SIZE < ( 1 < < MAX_FILE_SIZE_BITS ) ) ;
net_time = 0.0f ;
int hPort = CommandLine ( ) - > ParmValue ( " -port " , - 1 ) ;
if ( hPort = = - 1 )
{
hPort = CommandLine ( ) - > ParmValue ( " +port " , - 1 ) ; // check if they used +port by mistake
}
if ( hPort ! = - 1 )
{
hostport . SetValue ( hPort ) ;
}
// 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 ) ) ;
}
const char * ip = CommandLine ( ) - > ParmValue ( " -ip " ) ;
if ( 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
}
const int nProtocol = X360SecureNetwork ( ) ? IPPROTO_VDP : IPPROTO_UDP ;
// open client socket for masterserver
OpenSocketInternal ( NS_CLIENT , clientport . GetInt ( ) , PORT_SERVER , " client " , nProtocol , true ) ;
if ( bIsDedicated )
{
// set dedicated MP mode
NET_SetDedicated ( ) ;
}
else
{
// set SP mode
NET_ConfigLoopbackBuffers ( true ) ;
}
}
/*
= = = = = = = = = = = = = = = = = = = =
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 ) ;
# if defined(_WIN32)
if ( ! net_noip )
{
nError = WSACleanup ( ) ;
if ( nError )
{
Msg ( " Failed to complete WSACleanup = 0x%x. \n " , nError ) ;
}
# if defined(_X360)
nError = XOnlineCleanup ( ) ;
if ( nError ! = ERROR_SUCCESS )
{
Msg ( " Warning! Failed to complete XOnlineCleanup = 0x%x. \n " , nError ) ;
}
# endif // _X360
}
# 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 %s \n " , chan - > GetAddress ( ) , chan - > IsPlayback ( ) ? " (Demo) " : " " ) ;
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 ) ;
CFmtStrN < 128 > lan_str ;
# ifdef LINUX
lan_str . sprintf ( " , Lan %u " , net_sockets [ NS_SVLAN ] . nPort ) ;
# endif
ConMsg ( " - Ports: Client %u, Server %u, HLTV %u, Matchmaking %u, Systemlink %u%s \n " ,
net_sockets [ NS_CLIENT ] . nPort ,
net_sockets [ NS_SERVER ] . nPort ,
net_sockets [ NS_HLTV ] . nPort ,
net_sockets [ NS_MATCHMAKING ] . nPort ,
net_sockets [ NS_SYSTEMLINK ] . nPort ,
lan_str . Get ( ) ) ;
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 ) ;
}