//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// #ifdef _LINUX // linux has a multi-processing forked server mode. #include #include #include #include #include #include #include #include //#include #include //#include #include #include #include #include #include "isys.h" #include "dedicated.h" #include "engine_hlds_api.h" #include "filesystem.h" #include "tier0/dbg.h" #include "tier1/strtools.h" #include "tier0/icommandline.h" #include "tier2/socketcreator.h" #include "idedicatedexports.h" #include #include #include #include #include "mathlib/expressioncalculator.h" #define closesocket close #define WSAGetLastError() errno #define ioctlsocket ioctl // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static netadr_t net_local_adr; unsigned short NET_HostToNetShort( unsigned short us_in ) { return htons( us_in ); } unsigned short NET_NetToHostShort( unsigned short us_in ) { return ntohs( us_in ); } //----------------------------------------------------------------------------- // Purpose: // Input : *s - // *sadr - // Output : bool NET_StringToSockaddr //----------------------------------------------------------------------------- bool NET_StringToSockaddr( const char *s, struct sockaddr *sadr ) { char *colon; char copy[128]; Q_memset (sadr, 0, sizeof(*sadr)); ((struct sockaddr_in *)sadr)->sin_family = AF_INET; ((struct sockaddr_in *)sadr)->sin_port = 0; Q_strncpy (copy, s, sizeof( copy ) ); // strip off a trailing :port if present for (colon = copy ; *colon ; colon++) { if (*colon == ':') { *colon = 0; ((struct sockaddr_in *)sadr)->sin_port = NET_HostToNetShort((short)atoi(colon+1)); } } if (copy[0] >= '0' && copy[0] <= '9' && Q_strstr( copy, "." ) ) { *(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy); } else { 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; } /* ============= 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; } void NET_GetLocalAddress (void) { net_local_adr.Clear(); char buff[512]; 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); } #define MAX_STATUS_STRING_LENGTH 1024 #define MAX_INPUT_FROM_CHILD 2048 class CConnectedNetConsoleData { public: int m_nCharsInCommandBuffer; char m_pszInputCommandBuffer[MAX_INPUT_FROM_CHILD]; bool m_bAuthorized; // for password protection CConnectedNetConsoleData( void ) { m_nCharsInCommandBuffer = 0; m_bAuthorized = false; } }; class CParentProcessNetConsoleMgr : public ISocketCreatorListener { public: CSocketCreator m_Socket; netadr_t m_Address; char m_pPassword[256]; // if set bool m_bPasswordProtected; bool m_bActive; bool ShouldAcceptSocket( SocketHandle_t hSocket, const netadr_t &netAdr ) { return true; } void OnSocketAccepted( SocketHandle_t hSocket, const netadr_t &netAdr, void** ppData ) { CConnectedNetConsoleData *pData = new CConnectedNetConsoleData; if ( ! m_bPasswordProtected ) pData->m_bAuthorized = true; // no password, auto-auth *ppData = pData; } void OnSocketClosed( SocketHandle_t hSocket, const netadr_t &netAdr, void* pData ) { if ( pData ) free( pData ); } void RunFrame( void ); CParentProcessNetConsoleMgr( void ); void HandleInputChars( char const *pIn, int recvLen, CConnectedNetConsoleData *pData, int idx ); void SendString( char const *pString, int idx = -1 ); // send a string to all sockets or just one void Execute( CConnectedNetConsoleData *pData, int idx ); }; void CParentProcessNetConsoleMgr::SendString( char const *pString, int nidx ) { m_Socket.RunFrame(); int nCount = m_Socket.GetAcceptedSocketCount(); if ( nCount ) { // lets add the lf to any cr's char *pTmp = (char * ) stackalloc( strlen( pString ) * 2 + 1 ); char *oString = pTmp; char const *pIn = pString; while ( *pIn ) { if ( *pIn == '\n' ) *( oString++ ) = '\r'; *( oString++ ) = *( pIn++ ); } *( oString++ ) = 0; for ( int i = 0; i < nCount; i++ ) { if ( ( nidx == -1 ) || ( i == nidx ) ) { SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( i ); //const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i ); CConnectedNetConsoleData *pData = ( CConnectedNetConsoleData * ) m_Socket.GetAcceptedSocketData( i ); if ( pData->m_bAuthorized ) // no output to un-authed net consoles { send( hSocket, pTmp, oString - pTmp - 1, MSG_NOSIGNAL ); } } } } } CParentProcessNetConsoleMgr::CParentProcessNetConsoleMgr( void ) : m_Socket( this ) { m_bActive = false; m_bPasswordProtected = false; int nPassword = CommandLine()->FindParm( "-netconpassword" ); if ( nPassword ) { char const *pPassword = CommandLine()->GetParm( nPassword + 1 ); V_strncpy( m_pPassword, pPassword, sizeof( m_pPassword ) ); m_bPasswordProtected = true; } int nPort = CommandLine()->FindParm( "-netconport" ); if ( nPort ) { NET_GetLocalAddress(); char const *pPortNum = CommandLine()->GetParm( nPort + 1 ); char newBuf[256]; V_strncpy( newBuf, pPortNum, sizeof( newBuf ) ); char *pReplace = V_strstr( newBuf, "##" ); if ( pReplace ) { pReplace[0] = '0'; pReplace[1] = '0'; } m_Address = net_local_adr; int nPortNumber = EvaluateExpression( newBuf, -1 ); if ( nPortNumber > 0 ) { m_Address.SetPort( nPortNumber ); m_bActive = true; m_Socket.CreateListenSocket( m_Address, true ); } } } void CParentProcessNetConsoleMgr::RunFrame( void ) { // check for incoming data if (! m_bActive ) return; m_Socket.RunFrame(); int nCount = m_Socket.GetAcceptedSocketCount(); for ( int i = nCount - 1; i >= 0; i-- ) { SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( i ); // const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i ); CConnectedNetConsoleData *pData = ( CConnectedNetConsoleData * ) m_Socket.GetAcceptedSocketData( i ); char ch; int pendingLen = recv( hSocket, &ch, sizeof(ch), MSG_PEEK ); if ( pendingLen == -1 && SocketWouldBlock() ) continue; if ( pendingLen <= 0 ) // eof or error { m_Socket.CloseAcceptedSocket( i ); continue; } // find out how much we have to read unsigned long readLen; ioctlsocket( hSocket, FIONREAD, &readLen ); while( readLen > 0 ) { char recvBuf[256]; int recvLen = recv( hSocket, recvBuf , MIN( sizeof( recvBuf ) , readLen ), 0 ); if ( recvLen == 0 ) // socket was closed { m_Socket.CloseAcceptedSocket( i ); break; } if ( recvLen < 0 && !SocketWouldBlock() ) { break; } readLen -= recvLen; // now, lets write what we've got into the command buffer HandleInputChars( recvBuf, recvLen, pData, i ); } } } void CParentProcessNetConsoleMgr::HandleInputChars( char const *pIn, int recvLen, CConnectedNetConsoleData *pData, int idx ) { while( recvLen ) { switch( *pIn ) { case '\r': case '\n': { if ( pData->m_nCharsInCommandBuffer ) { pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer] = 0; Execute( pData, idx ); } pData->m_nCharsInCommandBuffer = 0; break; } default: { if ( pData->m_nCharsInCommandBuffer < MAX_INPUT_FROM_CHILD - 1 ) pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer++] = *pIn; break; } } pIn++; recvLen--; } } struct CServerInstance { pid_t m_nPid; int m_nSocketToChild; // "our" side of the socket connection int m_nNumCharsInInputBuffer; int m_nNumPlayers; char m_pszStatus[MAX_STATUS_STRING_LENGTH]; char m_pszMapName[MAX_PATH]; char m_pszInputBuffer[MAX_INPUT_FROM_CHILD]; bool m_bRunning; void ClearInputBuffer( void ) { m_nNumCharsInInputBuffer = 0; } void ResetStatus( void ) { m_pszMapName[0] = 0; m_nNumPlayers = 0; } CServerInstance( void ) { m_pszStatus[0] = 0; // clear status string m_bRunning = false; m_nSocketToChild = -1; ClearInputBuffer(); ResetStatus(); } void HandleSocketInput( void ); void ProcessInputFromChild( void ); }; #define MAX_CHILD_PROCESSSES 100 CParentProcessNetConsoleMgr *g_pParentProcessNetConsole; int g_nNumChildInstances; CServerInstance *g_pChildProcesses; static bool s_bQuit = false; static bool s_bDelayedQuit = false; typedef void (*CMDFN)( char const *pArgs, int nIdx ); struct CommandDescriptor { char const *m_pCmdName; CMDFN m_pCmdFn; char const *m_pCmdHelp; }; static char *va( char *format, ... ) { va_list argptr; static char string[8][512]; static int curstring = 0; curstring = ( curstring + 1 ) % 8; va_start (argptr, format); Q_vsnprintf( string[curstring], sizeof( string[curstring] ), format, argptr ); va_end (argptr); return string[curstring]; } static void s_DoStatusCmd( char const *pArgs, int nConsoleIdx ) { // print status g_pParentProcessNetConsole->SendString( "#status\n", nConsoleIdx ); for( int i = 0; i < g_nNumChildInstances; i++ ) { CServerInstance *pChild = g_pChildProcesses + i; g_pParentProcessNetConsole->SendString( va( "child %d\n", i ), nConsoleIdx ); if ( pChild && ( pChild->m_nSocketToChild != -1 ) ) { g_pParentProcessNetConsole->SendString( va( " pid : %d\n", i, pChild->m_nPid ), nConsoleIdx ); g_pParentProcessNetConsole->SendString( va( " map : %s\n", pChild->m_pszMapName ), nConsoleIdx ); g_pParentProcessNetConsole->SendString( va( " numplayers : %d\n", pChild->m_nNumPlayers ), nConsoleIdx ); } } g_pParentProcessNetConsole->SendString( "#end\n", nConsoleIdx ); } static void s_DoQuit( char const *pArgs, int nConsoleIdx ) { g_pParentProcessNetConsole->SendString( "Killing all children and exiting\n", nConsoleIdx ); for( int i = 0; i < g_nNumChildInstances; i++ ) { CServerInstance *pChild = g_pChildProcesses + i; if ( pChild && ( pChild->m_nSocketToChild != -1 ) ) { g_pParentProcessNetConsole->SendString( va( "killing child %d\n", i ), nConsoleIdx ); kill( pChild->m_nPid, SIGKILL ); } } s_bQuit = true; } static void s_DoBroadCastCmd( char const *pArgs, int nConsoleIdx ) { if ( ! pArgs ) { g_pParentProcessNetConsole->SendString( "Format of command is \"broadcast \"\n" ); } else { for( int i = 0; i < g_nNumChildInstances; i++ ) { CServerInstance *pChild = g_pChildProcesses + i; if ( pChild && ( pChild->m_nSocketToChild != -1 ) ) { send( pChild->m_nSocketToChild, pArgs, strlen( pArgs ), MSG_NOSIGNAL ); send( pChild->m_nSocketToChild, "\n", 1, MSG_NOSIGNAL ); } } } } static void s_DoShutdown( char const *pArgs, int nConsoleIdx ) { s_bDelayedQuit = ! s_bDelayedQuit; if ( s_bDelayedQuit ) { g_pParentProcessNetConsole->SendString( "Server will shutdown when all games are finished and children have exited.\n" ); } else { g_pParentProcessNetConsole->SendString( "Server shutdown cancelled.\n" ); } for( int i = 0; i < g_nNumChildInstances; i++ ) { CServerInstance *pChild = g_pChildProcesses + i; if ( pChild && ( pChild->m_nSocketToChild != -1 ) ) { if ( pChild->m_nNumPlayers == 0 ) { kill( pChild->m_nPid, SIGKILL ); } } } } static void s_DoFind( char const *pArgs, int nConsoleIdx ); static CommandDescriptor s_CmdTable[]={ { "status", s_DoStatusCmd, "List the status of all subprocesses." }, { "broadcast", s_DoBroadCastCmd, "Send a command to all subprocesses." }, { "find", s_DoFind, "find commands containing a string." }, { "shutdown", s_DoShutdown, "Tell the server shutdown once all players have left. This is a toggle." }, { "quit", s_DoQuit, "immediately shut down the server and all its child processes." }, }; static void s_DoFind( char const *pArgs, int nConsoleIdx ) { for( int i = 0; i < ARRAYSIZE( s_CmdTable ); i++ ) { if ( ( pArgs[0] == 0 ) || ( V_stristr( s_CmdTable[i].m_pCmdName, pArgs ) ) ) { g_pParentProcessNetConsole->SendString( va( "%s:\t%s\n", s_CmdTable[i].m_pCmdName, s_CmdTable[i].m_pCmdHelp) , nConsoleIdx ); } } } void CParentProcessNetConsoleMgr::Execute( CConnectedNetConsoleData *pData, int idx ) { if ( memcmp( pData->m_pszInputCommandBuffer, "PASS ", 5 ) == 0 ) { if ( V_strcmp( pData->m_pszInputCommandBuffer + 5, m_pPassword ) == 0 ) { pData->m_bAuthorized = true; } else { // bad password Warning( "Bad password attempt from net console\n" ); pData->m_bAuthorized = false; } } else { if ( pData->m_bAuthorized ) { char const *pCmd = pData->m_pszInputCommandBuffer; pCmd += strspn( pCmd, " \t" ); char const *pArgs = strchr( pCmd, ' ' ); int nCmdLen; if ( pArgs ) { nCmdLen = pArgs - pCmd; pArgs += strspn( pArgs, " \t" ); // skip to first char of first word } else { nCmdLen = strlen( pCmd ); pArgs = pCmd + strlen( pCmd ); // point at trailing 0 bytes } for( int i = 0; i < ARRAYSIZE( s_CmdTable ); i++ ) { char const *pTblCmd = s_CmdTable[i].m_pCmdName; if ( ( strlen( pTblCmd ) == nCmdLen ) && ( memcmp( pTblCmd, pCmd, nCmdLen ) == 0 ) ) { // found it ( *s_CmdTable[i].m_pCmdFn )( pArgs, idx ); break; } } } else { SendString( "This server is password protected. Enter PASS for access\n", idx ); } } } static void HandleDeadChildProcesses( void ) { for(;;) { int nStatus; pid_t nWait = waitpid( -1, &nStatus, WNOHANG ); if ( nWait > 0 ) { // find the process that exited CServerInstance *pFound = NULL; int nFound = -1; for( int i = 0; i < g_nNumChildInstances; i++ ) { if ( g_pChildProcesses[i].m_nPid == nWait ) { pFound = g_pChildProcesses + i; nFound = i; break; } } if ( ! pFound ) { Warning( "unknown child process %d exited?\n", nWait ); } else { if ( WIFEXITED( nStatus ) ) { Msg( "Child %d exited with status %d\n", nFound, WEXITSTATUS( nStatus ) ); } if ( WIFSIGNALED( nStatus ) ) { Msg( "Child %d aborted with signal %d\n", nFound, WTERMSIG( nStatus ) ); } if ( WCOREDUMP( nStatus ) ) { Msg( "Child wrote a core dump\n"); } pFound->m_bRunning = false; if ( pFound->m_nSocketToChild != -1 ) { close( pFound->m_nSocketToChild ); pFound->m_nSocketToChild = -1; } } } else { break; // no dead children } } } #define MAX_ACTIVE_PARENT_NETCONSOLE_SOCKETS 20 static void ServiceChildProcesses( void ) { // for any children that aren't running (or not running yet), start them pollfd pollFds[MAX_CHILD_PROCESSSES + 1 + MAX_ACTIVE_PARENT_NETCONSOLE_SOCKETS ]; int nPoll = 0; int nNumRunning = 0; for( int i = 0; i < g_nNumChildInstances; i++ ) { if ( g_pChildProcesses[i].m_bRunning == false ) { if (! s_bDelayedQuit ) { int nSockets[2]; int nRslt = socketpair( AF_UNIX, SOCK_STREAM, 0, nSockets ); if ( nRslt != 0 ) { Error( "socket pair returned error errno = %d\n", errno ); } pid_t nChild = fork(); if ( nChild == 0 ) // are we the forked child? { //ResetBaseTime(); // start plat_float time over at 0 for precision PerformCommandLineSubstitutions( i + 1 ); close( nSockets[1] ); engine->SetSubProcessID( i + 1, nSockets[0] ); g_nSubProcessId = i + 1; RunServer( true ); syscall( SYS_exit, 0 ); // we are not going to perform a normal c++ exit. We _dont_ want to run destructors, etc. } else { g_pChildProcesses[i].m_nPid = nChild; g_pChildProcesses[i].m_pszStatus[0] = 0; g_pChildProcesses[i].m_bRunning = true; close( nSockets[0] ); g_pChildProcesses[i].m_nSocketToChild = nSockets[1]; } } } else { nNumRunning++; } if ( g_pChildProcesses[i].m_nSocketToChild != -1 ) { pollFds[nPoll].fd = g_pChildProcesses[i].m_nSocketToChild; pollFds[nPoll].events = POLLIN | POLLERR | POLLHUP; pollFds[nPoll].revents = 0; nPoll++; } } if ( s_bDelayedQuit && ( nNumRunning == 0 ) ) { _exit( 0 ); } // now, wait for activity on any of our sockets or stdin // pollFds[nPoll].fd = STDIN_FILENO; // pollFds[nPoll].events = POLLIN; // pollFds[nPoll].revents = 0; // nPoll++; if ( g_pParentProcessNetConsole && ( g_pParentProcessNetConsole->m_bActive ) ) { pollFds[nPoll].fd = g_pParentProcessNetConsole->m_Socket.m_hListenSocket; pollFds[nPoll].events = POLLIN; pollFds[nPoll].revents = 0; nPoll++; int nCount = g_pParentProcessNetConsole->m_Socket.GetAcceptedSocketCount(); for( int i = 0; ( i < nCount ) && ( nPoll < ARRAYSIZE( pollFds ) ); i++ ) { SocketHandle_t hSocket = g_pParentProcessNetConsole->m_Socket.GetAcceptedSocketHandle( i ); pollFds[nPoll].fd = hSocket; pollFds[nPoll].events = POLLIN; pollFds[nPoll].revents = 0; nPoll++; } } int nPollResult = poll( pollFds, nPoll, 10 * 1000 ); // wait up to 10 seconds. Could wait forever, really // check for activity on the sockets from our children int np = 0; for( int i = 0; i < g_nNumChildInstances; i++ ) { if ( g_pChildProcesses[i].m_nSocketToChild != -1 ) { if ( pollFds[np].revents & POLLIN ) // data ready to read? { g_pChildProcesses[i].HandleSocketInput(); } np++; } } // see if any children have exited HandleDeadChildProcesses(); g_pParentProcessNetConsole->RunFrame(); } void RunServerSubProcesses( int nNumChildren ) { g_nNumChildInstances = nNumChildren; g_pChildProcesses = new CServerInstance[g_nNumChildInstances]; g_pParentProcessNetConsole = new CParentProcessNetConsoleMgr; while( ! s_bQuit ) { ServiceChildProcesses(); } _exit( 0 ); } static bool DecodeParam( char const *pParamName, char const *pInput, char const **pOutPtr ) { // if the left of the string matches pParamName, return the right of the string else return null int nPLen = strlen( pParamName ); if ( memcmp( pParamName, pInput, nPLen ) == 0 ) { *pOutPtr= pInput + nPLen; } else { *pOutPtr = NULL; } return ( *pOutPtr ); } void CServerInstance::ProcessInputFromChild( void ) { if ( m_pszInputBuffer[0] == '#' ) // spew? { puts( m_pszInputBuffer ); } else { char *pSpace = strchr( m_pszInputBuffer, ' ' ); if ( pSpace ) { *( pSpace++ ) = 0; pSpace += strspn( pSpace, " \t" ); } else { pSpace = m_pszInputBuffer + strlen( m_pszInputBuffer ); } if ( !strcmp( m_pszInputBuffer, "status" ) ) { CUtlStringList statusRecords; V_SplitString( pSpace, ";", statusRecords ); for( int i = 0; i < statusRecords.Count(); i++ ) { char const *pRecord = statusRecords[i]; char const *pParm; if ( DecodeParam( "map=", pRecord, &pParm ) ) { V_strncpy( m_pszMapName, pParm, sizeof( m_pszMapName ) ); } else if ( DecodeParam( "players=", pRecord, &pParm ) ) { m_nNumPlayers = atoi( pParm ); } } } else { Warning("got unknown cmd %s args %s\n", m_pszInputBuffer, pSpace ); } } } void CServerInstance::HandleSocketInput( void ) { char *pDest = m_pszInputBuffer + m_nNumCharsInInputBuffer; int nRead = recv( m_nSocketToChild, pDest, sizeof( m_pszInputBuffer ) - m_nNumCharsInInputBuffer, MSG_DONTWAIT ); if ( nRead > 0 ) { m_nNumCharsInInputBuffer += nRead; if ( m_pszInputBuffer[m_nNumCharsInInputBuffer - 1] == 0 ) { ProcessInputFromChild(); m_nNumCharsInInputBuffer = 0; } if ( m_nNumCharsInInputBuffer == MAX_INPUT_FROM_CHILD ) m_nNumCharsInInputBuffer = 0; } } #endif //linux