/* ** Command & Conquer Generals(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: PingThread.cpp ////////////////////////////////////////////////////// // Ping thread // Author: Matthew D. Campbell, August 2002 #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include // This one has to be here. Prevents collisions with windsock2.h #include "GameNetwork/GameSpy/PingThread.h" #include "mutex.h" #include "thread.h" #include "Common/StackDump.h" #include "Common/SubsystemInterface.h" //------------------------------------------------------------------------- static const Int NumWorkerThreads = 10; typedef std::queue RequestQueue; typedef std::queue ResponseQueue; class PingThreadClass; class Pinger : public PingerInterface { public: virtual ~Pinger(); Pinger(); virtual void startThreads( void ); virtual void endThreads( void ); virtual Bool areThreadsRunning( void ); virtual void addRequest( const PingRequest& req ); virtual Bool getRequest( PingRequest& resp ); virtual void addResponse( const PingResponse& resp ); virtual Bool getResponse( PingResponse& resp ); virtual Bool arePingsInProgress( void ); virtual Int getPing( AsciiString hostname ); virtual void clearPingMap( void ); virtual AsciiString getPingString( Int timeout ); private: MutexClass m_requestMutex; MutexClass m_responseMutex; MutexClass m_pingMapMutex; RequestQueue m_requests; ResponseQueue m_responses; Int m_requestCount; Int m_responseCount; std::map m_pingMap; PingThreadClass *m_workerThreads[NumWorkerThreads]; }; PingerInterface* PingerInterface::createNewPingerInterface( void ) { return NEW Pinger; } PingerInterface *ThePinger; //------------------------------------------------------------------------- class PingThreadClass : public ThreadClass { public: PingThreadClass() : ThreadClass() {} void Thread_Function(); private: Int doPing( UnsignedInt IP, Int timeout ); }; //------------------------------------------------------------------------- Pinger::Pinger() : m_requestCount(0), m_responseCount(0) { for (Int i=0; iExecute(); } } void Pinger::endThreads( void ) { for (Int i=0; iIs_Running()) return true; } } return false; } void Pinger::addRequest( const PingRequest& req ) { MutexClass::LockClass m(m_requestMutex); ++m_requestCount; m_requests.push(req); } Bool Pinger::getRequest( PingRequest& req ) { MutexClass::LockClass m(m_requestMutex, 0); if (m.Failed()) return false; if (m_requests.empty()) return false; req = m_requests.front(); m_requests.pop(); return true; } void Pinger::addResponse( const PingResponse& resp ) { { MutexClass::LockClass m(m_pingMapMutex); m_pingMap[resp.hostname] = resp.avgPing; } { MutexClass::LockClass m(m_responseMutex); ++m_responseCount; m_responses.push(resp); } } Bool Pinger::getResponse( PingResponse& resp ) { MutexClass::LockClass m(m_responseMutex, 0); if (m.Failed()) return false; if (m_responses.empty()) return false; resp = m_responses.front(); m_responses.pop(); return true; } Bool Pinger::arePingsInProgress( void ) { return (m_requestCount != m_responseCount); } Int Pinger::getPing( AsciiString hostname ) { MutexClass::LockClass m(m_pingMapMutex, 0); if (m.Failed()) return false; std::map::const_iterator it = m_pingMap.find(hostname.str()); if (it != m_pingMap.end()) return it->second; return -1; } void Pinger::clearPingMap( void ) { MutexClass::LockClass m(m_pingMapMutex); m_pingMap.clear(); } AsciiString Pinger::getPingString( Int timeout ) { MutexClass::LockClass m(m_pingMapMutex); AsciiString pingString; AsciiString tmp; for (std::map::const_iterator it = m_pingMap.begin(); it != m_pingMap.end(); ++it) { Int ping = it->second; if (ping < 0 || ping > timeout) ping = timeout; ping = ping * 255 / timeout; tmp.format("%2.2X", ping); pingString.concat(tmp); } return pingString; } //------------------------------------------------------------------------- void PingThreadClass::Thread_Function() { try { _set_se_translator( DumpExceptionInfo ); // Hook that allows stack trace. PingRequest req; WSADATA wsaData; // Fire up winsock (prob already done, but doesn't matter) WORD wVersionRequested = MAKEWORD(1, 1); WSAStartup( wVersionRequested, &wsaData ); while ( running ) { // deal with requests if (ThePinger->getRequest(req)) { // resolve the hostname const char *hostnameBuffer = req.hostname.c_str(); UnsignedInt IP = 0xFFFFFFFF; if (isdigit(hostnameBuffer[0])) { IP = inet_addr(hostnameBuffer); in_addr hostNode; hostNode.s_addr = IP; DEBUG_LOG(("pinging %s - IP = %s\n", hostnameBuffer, inet_ntoa(hostNode) )); } else { HOSTENT *hostStruct; in_addr *hostNode; hostStruct = gethostbyname(hostnameBuffer); if (hostStruct == NULL) { DEBUG_LOG(("pinging %s - host lookup failed\n", hostnameBuffer)); // Even though this failed to resolve IP, still need to send a // callback. IP = 0xFFFFFFFF; // flag for IP resolve failed } hostNode = (in_addr *) hostStruct->h_addr; IP = hostNode->s_addr; DEBUG_LOG(("pinging %s IP = %s\n", hostnameBuffer, inet_ntoa(*hostNode) )); } // do ping Int totalPing = 0; Int goodReps = 0; Int reps = req.repetitions; while (reps-- && running && IP != 0xFFFFFFFF) { Int ping = doPing(IP, req.timeout); if (ping >= 0) { totalPing += ping; ++goodReps; } // end our timeslice Switch_Thread(); } if (!goodReps) totalPing = -1; else totalPing = totalPing / goodReps; PingResponse resp; resp.hostname = req.hostname; resp.avgPing = totalPing; resp.repetitions = goodReps; ThePinger->addResponse(resp); } // end our timeslice Switch_Thread(); } WSACleanup(); } catch ( ... ) { DEBUG_CRASH(("Exception in ping thread!")); } } //------------------------------------------------------------------------- //------------------------------------------------------------------------- //------------------------------------------------------------------------- //------------------------------------------------------------------------- //------------------------------------------------------------------------- //------------------------------------------------------------------------- HANDLE WINAPI IcmpCreateFile(VOID); /* INVALID_HANDLE_VALUE on error */ BOOL WINAPI IcmpCloseHandle(HANDLE IcmpHandle); /* FALSE on error */ /* Note 2: For the most part, you can refer to RFC 791 for detials * on how to fill in values for the IP option information structure. */ typedef struct ip_option_information { UnsignedByte Ttl; /* Time To Live (used for traceroute) */ UnsignedByte Tos; /* Type Of Service (usually 0) */ UnsignedByte Flags; /* IP header flags (usually 0) */ UnsignedByte OptionsSize; /* Size of options data (usually 0, max 40) */ UnsignedByte FAR *OptionsData; /* Options data buffer */ } IPINFO, *PIPINFO, FAR *LPIPINFO; /* Note 1: The Reply Buffer will have an array of ICMP_ECHO_REPLY * structures, followed by options and the data in ICMP echo reply * datagram received. You must have room for at least one ICMP * echo reply structure, plus 8 bytes for an ICMP header. */ typedef struct icmp_echo_reply { UnsignedInt Address; /* source address */ ////////UnsignedInt Status; /* IP status value (see below) */ UnsignedInt RTTime; /* Round Trip Time in milliseconds */ UnsignedShort DataSize; /* reply data size */ UnsignedShort Reserved; /* */ void FAR *Data; /* reply data buffer */ struct ip_option_information Options; /* reply options */ } ICMPECHO, *PICMPECHO, FAR *LPICMPECHO; DWORD WINAPI IcmpSendEcho( HANDLE IcmpHandle, /* handle returned from IcmpCreateFile() */ UnsignedInt DestAddress, /* destination IP address (in network order) */ LPVOID RequestData, /* pointer to buffer to send */ WORD RequestSize, /* length of data in buffer */ LPIPINFO RequestOptns, /* see Note 2 */ LPVOID ReplyBuffer, /* see Note 1 */ DWORD ReplySize, /* length of reply (must allow at least 1 reply) */ DWORD Timeout /* time in milliseconds to wait for reply */ ); #define IP_STATUS_BASE 11000 #define IP_SUCCESS 0 #define IP_BUF_TOO_SMALL (IP_STATUS_BASE + 1) #define IP_DEST_NET_UNREACHABLE (IP_STATUS_BASE + 2) #define IP_DEST_HOST_UNREACHABLE (IP_STATUS_BASE + 3) #define IP_DEST_PROT_UNREACHABLE (IP_STATUS_BASE + 4) #define IP_DEST_PORT_UNREACHABLE (IP_STATUS_BASE + 5) #define IP_NO_RESOURCES (IP_STATUS_BASE + 6) #define IP_BAD_OPTION (IP_STATUS_BASE + 7) #define IP_HW_ERROR (IP_STATUS_BASE + 8) #define IP_PACKET_TOO_BIG (IP_STATUS_BASE + 9) #define IP_REQ_TIMED_OUT (IP_STATUS_BASE + 10) #define IP_BAD_REQ (IP_STATUS_BASE + 11) #define IP_BAD_ROUTE (IP_STATUS_BASE + 12) #define IP_TTL_EXPIRED_TRANSIT (IP_STATUS_BASE + 13) #define IP_TTL_EXPIRED_REASSEM (IP_STATUS_BASE + 14) #define IP_PARAM_PROBLEM (IP_STATUS_BASE + 15) #define IP_SOURCE_QUENCH (IP_STATUS_BASE + 16) #define IP_OPTION_TOO_BIG (IP_STATUS_BASE + 17) #define IP_BAD_DESTINATION (IP_STATUS_BASE + 18) #define IP_ADDR_DELETED (IP_STATUS_BASE + 19) #define IP_SPEC_MTU_CHANGE (IP_STATUS_BASE + 20) #define IP_MTU_CHANGE (IP_STATUS_BASE + 21) #define IP_UNLOAD (IP_STATUS_BASE + 22) #define IP_GENERAL_FAILURE (IP_STATUS_BASE + 50) #define MAX_IP_STATUS IP_GENERAL_FAILURE #define IP_PENDING (IP_STATUS_BASE + 255) #define BUFSIZE 8192 #define DEFAULT_LEN 32 #define LOOPLIMIT 4 #define DEFAULT_TTL 64 Int PingThreadClass::doPing(UnsignedInt IP, Int timeout) { /* * Initialize default settings */ IPINFO stIPInfo, *lpstIPInfo; HANDLE hICMP, hICMP_DLL; int i, j, nDataLen, nLoopLimit, nTimeOut, nTTL, nTOS; DWORD dwReplyCount; ///////IN_ADDR stDestAddr; BOOL fRet, fDontStop; ///BOOL fTraceRoute; nDataLen = DEFAULT_LEN; nLoopLimit = LOOPLIMIT; nTimeOut = timeout; fDontStop = FALSE; lpstIPInfo = NULL; nTTL = DEFAULT_TTL; nTOS = 0; Int pingTime = -1; // in case of error char achReqData[BUFSIZE]; char achRepData[sizeof(ICMPECHO) + BUFSIZE]; HANDLE ( WINAPI *lpfnIcmpCreateFile )( VOID ) = NULL; BOOL ( WINAPI *lpfnIcmpCloseHandle )( HANDLE ) = NULL; DWORD (WINAPI *lpfnIcmpSendEcho)(HANDLE, DWORD, LPVOID, WORD, LPVOID, LPVOID, DWORD, DWORD) = NULL; /* * Load the ICMP.DLL */ hICMP_DLL = LoadLibrary("ICMP.DLL"); if (hICMP_DLL == 0) { DEBUG_LOG(("LoadLibrary() failed: Unable to locate ICMP.DLL!\n")); goto cleanup; } /* * Get pointers to ICMP.DLL functions */ lpfnIcmpCreateFile = (void * (__stdcall *)(void))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpCreateFile"); lpfnIcmpCloseHandle = (int (__stdcall *)(void *))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpCloseHandle"); lpfnIcmpSendEcho = (unsigned long (__stdcall *)(void *, unsigned long, void *, unsigned short, void *, void *, unsigned long, unsigned long))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpSendEcho" ); if ((!lpfnIcmpCreateFile) || (!lpfnIcmpCloseHandle) || (!lpfnIcmpSendEcho)) { DEBUG_LOG(("GetProcAddr() failed for at least one function.\n")); goto cleanup; } /* * IcmpCreateFile() - Open the ping service */ hICMP = (HANDLE) lpfnIcmpCreateFile(); if (hICMP == INVALID_HANDLE_VALUE) { DEBUG_LOG(("IcmpCreateFile() failed")); goto cleanup; } /* * Init data buffer printable ASCII * 32 (space) through 126 (tilde) */ for (j = 0, i = 32; j < nDataLen; j++, i++) { if (i >= 126) i = 32; achReqData[j] = i; } /* * Init IPInfo structure */ lpstIPInfo = &stIPInfo; stIPInfo.Ttl = nTTL; stIPInfo.Tos = nTOS; stIPInfo.Flags = 0; stIPInfo.OptionsSize = 0; stIPInfo.OptionsData = NULL; /* * IcmpSendEcho() - Send the ICMP Echo Request * and read the Reply */ dwReplyCount = lpfnIcmpSendEcho( hICMP, IP, achReqData, nDataLen, lpstIPInfo, achRepData, sizeof(achRepData), nTimeOut); if (dwReplyCount != 0) { //////////IN_ADDR stDestAddr; DWORD dwStatus; pingTime = (*(UnsignedInt *) & (achRepData[8])); // I've seen the ping time bigger than the timeout by a little // bit. How lame. if (pingTime > timeout) pingTime = timeout; dwStatus = *(DWORD *) & (achRepData[4]); if (dwStatus != IP_SUCCESS) { DEBUG_LOG(("ICMPERR: %d\n", dwStatus)); } } else { DEBUG_LOG(("IcmpSendEcho() failed: %d\n", dwReplyCount)); // Ok we didn't get a packet, just say everything's OK // and the time was -1 pingTime = -1; goto cleanup; } /* * IcmpCloseHandle - Close the ICMP handle */ fRet = lpfnIcmpCloseHandle(hICMP); if (fRet == FALSE) { DEBUG_LOG(("Error closing ICMP handle\n")); } // Say what you will about goto's but it's handy for stuff like this cleanup: // Shut down... if (hICMP_DLL) FreeLibrary((HINSTANCE)hICMP_DLL); return pingTime; } //-------------------------------------------------------------------------