diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/net_ws.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/net_ws.cpp')
| -rw-r--r-- | engine/net_ws.cpp | 3457 |
1 files changed, 3457 insertions, 0 deletions
diff --git a/engine/net_ws.cpp b/engine/net_ws.cpp new file mode 100644 index 0000000..3c49ab6 --- /dev/null +++ b/engine/net_ws.cpp @@ -0,0 +1,3457 @@ +//========= 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" + +// 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( 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( 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( 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() ) + { + 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( 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( 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( 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( 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() || 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( 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( 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 + +#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() || 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 + } + + 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( 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( 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 ); +} |