//========= 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 net_sockets; // the 4 sockets, Server, Client, HLTV, Matchmaking static CUtlVector 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 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(netchan) ) == s_NetChannels.InvalidIndex() ) { DevMsg(1, "NET_CloseNetChannel: unknown channel.\n"); return; } s_NetChannels.FindAndRemove( static_cast(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; isource], 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 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; itime) > 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 g_NetScratchBuffers; void NET_ProcessSocket( int sock, IConnectionlessPacketHandler *handler ) { VPROF_BUDGET( "NET_ProcessSocket", VPROF_BUDGETGROUP_OTHER_NETWORKING ); netpacket_t * packet; Assert ( (sock >= 0) && (sock= 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( 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 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() ) 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<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 ); }