/* ** Command & Conquer Renegade(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 . */ // // Filename: netutil.cpp // Project: wwnet // Author: Tom Spencer-Smith // Date: June 1998 // Description: // //----------------------------------------------------------------------------- #include "netutil.h" // I WANNA BE FIRST! #include #include #include "miscutil.h" #include "mathutil.h" #include "singlepl.h" #include "wwpacket.h" #include "wwdebug.h" #include "ffactory.h" #include "ini.h" #include "systimer.h" #include "fromaddress.h" // // class statics // //const WORD cNetUtil::WS_VERSION_REQD = MAKEWORD(1, 1); // Winsock 1.1 //USHORT cNetUtil::HeaderBytes; //USHORT cNetUtil::MaxPacketAppDataSize = MAX_LAN_PACKET_APP_DATA_SIZE; UINT cNetUtil::DefaultResendTimeoutMs = 200; // used for singleplayer bool cNetUtil::IsInternet = false; static const int INVALID_VALUE = -999; const USHORT cNetUtil::NETSTATS_SAMPLE_TIME_MS = 2000; const USHORT cNetUtil::KEEPALIVE_TIMEOUT_MS = 2000; const USHORT cNetUtil::MAX_RESENDS = 50; const USHORT cNetUtil::MULTI_SENDS = 10; const USHORT cNetUtil::RESEND_TIMEOUT_LAN_MS = 300; const USHORT cNetUtil::RESEND_TIMEOUT_INTERNET_MS = 500; const ULONG cNetUtil::CLIENT_CONNECTION_LOSS_TIMEOUT = 15000; // Milliseconds til client gives up on server const ULONG cNetUtil::SERVER_CONNECTION_LOSS_TIMEOUT = 15000; // Milliseconds til server gives up on client const ULONG cNetUtil::SERVER_CONNECTION_LOSS_TIMEOUT_LOADING_ALLOWANCE = 45000; // Milliseconds extra allowed til server gives up on loading client. //int cNetUtil::DefaultMultiSends = INVALID_VALUE; //int cNetUtil::DefaultMaxResends = INVALID_VALUE; //int cNetUtil::DefaultKeepaliveTimeoutMs = INVALID_VALUE; //int cNetUtil::DesiredSendBufferSizeBytes = INVALID_VALUE; //int cNetUtil::DesiredReceiveBufferSizeBytes = INVALID_VALUE; //int cNetUtil::DefaultServerPort = INVALID_VALUE; //int cNetUtil::MaxReceiveTimeMs = INVALID_VALUE; //float cNetUtil::PriorityToleranceDownwards = INVALID_VALUE; //float cNetUtil::PriorityToleranceUpwards = INVALID_VALUE; //float cNetUtil::MaxTPCorrectionDownwards = INVALID_VALUE; //float cNetUtil::MaxTPCorrectionUpwards = INVALID_VALUE; //float cNetUtil::PriorityNoiseFactor = INVALID_VALUE; //float cNetUtil::InitialThresholdPriority = INVALID_VALUE; //float cNetUtil::PriorityGrowthPerSecond = INVALID_VALUE; char cNetUtil::WorkingAddressBuffer[] = ""; //----------------------------------------------------------------------------- // // Macro // #define ADD_CASE(exp) case exp: ::sprintf(error_msg, #exp); break; void cNetUtil::Wsa_Error(LPCSTR sFile, unsigned uLine) { WWDEBUG_SAY(("* %s:%d: WSA function returned error code: %s\n", sFile, uLine, Winsock_Error_Text(::WSAGetLastError()))); DIE; } //----------------------------------------------------------------------------- // // Just get the text for the specified error code. // const char * cNetUtil::Winsock_Error_Text(int error_code) { static char error_msg[500]; switch (error_code) { // // Windows Sockets definitions of regular Microsoft C error constants // ADD_CASE(WSAEINTR) ADD_CASE(WSAEBADF) ADD_CASE(WSAEACCES) ADD_CASE(WSAEFAULT) ADD_CASE(WSAEINVAL) ADD_CASE(WSAEMFILE) // // Windows Sockets definitions of regular Berkeley error constants // ADD_CASE(WSAEWOULDBLOCK) ADD_CASE(WSAEINPROGRESS) ADD_CASE(WSAEALREADY) ADD_CASE(WSAENOTSOCK) ADD_CASE(WSAEDESTADDRREQ) ADD_CASE(WSAEMSGSIZE) ADD_CASE(WSAEPROTOTYPE) ADD_CASE(WSAENOPROTOOPT) ADD_CASE(WSAEPROTONOSUPPORT) ADD_CASE(WSAESOCKTNOSUPPORT) ADD_CASE(WSAEOPNOTSUPP) ADD_CASE(WSAEPFNOSUPPORT) ADD_CASE(WSAEAFNOSUPPORT) ADD_CASE(WSAEADDRINUSE) ADD_CASE(WSAEADDRNOTAVAIL) ADD_CASE(WSAENETDOWN) ADD_CASE(WSAENETUNREACH) ADD_CASE(WSAENETRESET) ADD_CASE(WSAECONNABORTED) ADD_CASE(WSAECONNRESET) ADD_CASE(WSAENOBUFS) ADD_CASE(WSAEISCONN) ADD_CASE(WSAENOTCONN) ADD_CASE(WSAESHUTDOWN) ADD_CASE(WSAETOOMANYREFS) ADD_CASE(WSAETIMEDOUT) ADD_CASE(WSAECONNREFUSED) ADD_CASE(WSAELOOP) ADD_CASE(WSAENAMETOOLONG) ADD_CASE(WSAEHOSTDOWN) ADD_CASE(WSAEHOSTUNREACH) ADD_CASE(WSAENOTEMPTY) ADD_CASE(WSAEPROCLIM) ADD_CASE(WSAEUSERS) ADD_CASE(WSAEDQUOT) ADD_CASE(WSAESTALE) ADD_CASE(WSAEREMOTE) // // Extended Windows Sockets error constant definitions /// ADD_CASE(WSASYSNOTREADY) ADD_CASE(WSAVERNOTSUPPORTED) ADD_CASE(WSANOTINITIALISED) ADD_CASE(WSAEDISCON) default: ::sprintf(error_msg, "Unknown Winsock Error (%d)", error_code); break; } return(error_msg); } /* int g_c_wouldblock = 0; int g_c_nobufs = 0; */ //----------------------------------------------------------------------------- bool cNetUtil::Send_Resource_Failure(LPCSTR sFile, unsigned uLine, int ret_code) { bool return_code = false; if (ret_code == SOCKET_ERROR) { int wsa_error = ::WSAGetLastError(); if (wsa_error == WSAEWOULDBLOCK || wsa_error == WSAENOBUFS) { /* if (wsa_error == WSAEWOULDBLOCK) { g_c_wouldblock++; } else { g_c_nobufs++; } */ return_code = true; } else { Wsa_Error(sFile, uLine); } } else { return_code = false; } return return_code; } //----------------------------------------------------------------------------- bool cNetUtil::Would_Block(LPCSTR sFile, unsigned uLine, int ret_code) { bool retcode = false; if (ret_code == SOCKET_ERROR) { if (::WSAGetLastError() == WSAEWOULDBLOCK) { retcode = true; } else { Wsa_Error(sFile, uLine); retcode = false; } } else { retcode = false; } return retcode; } //----------------------------------------------------------------------------- // // Returns up to max_addresses adapter addresses for the local host // int cNetUtil::Get_Local_Tcpip_Addresses(SOCKADDR_IN ip_address[], USHORT max_addresses) { WWDEBUG_SAY(("cNetUtil::Get_Local_Tcpip_Addresses:\n")); // // Get the local hostname // char local_host_name[200]; WSA_CHECK(::gethostname(local_host_name, sizeof(local_host_name))); WWDEBUG_SAY((" Host name is %s\n", local_host_name)); // // Resolve hostname for local adapter addresses. This does // a DNS lookup (name resolution) // LPHOSTENT p_hostent = ::gethostbyname(local_host_name); int num_adapters = 0; if (p_hostent == NULL) { num_adapters = 0; } else { while (num_adapters < max_addresses && p_hostent->h_addr_list[num_adapters] != NULL) { ZeroMemory(&ip_address[num_adapters], sizeof(SOCKADDR_IN)); ip_address[num_adapters].sin_family = AF_INET; ip_address[num_adapters].sin_addr.s_addr = *((u_long *) (p_hostent->h_addr_list[num_adapters])); WWDEBUG_SAY((" Address: %s\n", Address_To_String(ip_address[num_adapters].sin_addr.s_addr))); num_adapters++; } } return num_adapters; } //----------------------------------------------------------------------------- bool cNetUtil::Is_Same_Address(LPSOCKADDR_IN p_address1, const SOCKADDR_IN* p_address2) { // // C disallows comparison of structs... // WWASSERT(!cSinglePlayerData::Is_Single_Player()); WWASSERT(p_address1 != NULL); WWASSERT(p_address2 != NULL); return p_address1->sin_addr.s_addr == p_address2->sin_addr.s_addr && p_address1->sin_port == p_address2->sin_port; } //------------------------------------------------------------------------------- void cNetUtil::Address_To_String(LPSOCKADDR_IN p_address, char * str, UINT len, USHORT & port) { WWASSERT(p_address != NULL); WWASSERT(str != NULL); char temp_str[1000]; ::strcpy(temp_str, ::inet_ntoa(p_address->sin_addr)); port = ::ntohs(p_address->sin_port); WWASSERT(::strlen(temp_str) <= len); ::strcpy(str, temp_str); } //------------------------------------------------------------------------------- LPCSTR cNetUtil::Address_To_String(ULONG ip) { IN_ADDR in_addr; in_addr.s_addr = ip; char * p = ::inet_ntoa(in_addr); if (p == NULL) { ::sprintf(WorkingAddressBuffer, "Invalid ip (%u)", ip); } else { ::strcpy(WorkingAddressBuffer, p); } return WorkingAddressBuffer; } //------------------------------------------------------------------------------- void cNetUtil::String_To_Address(LPSOCKADDR_IN p_address, LPCSTR str, USHORT port) { WWASSERT(p_address != NULL); ZeroMemory(p_address, sizeof(SOCKADDR_IN)); p_address->sin_family = AF_INET; p_address->sin_addr.s_addr = ::inet_addr(str); p_address->sin_port = ::htons(port); WWASSERT(p_address->sin_addr.s_addr != INADDR_NONE); } //------------------------------------------------------------------------------- bool cNetUtil::Is_Tcpip_Present(void) { // // N.B. I tested EnumProtocols and found it unreliable. // bool retcode = true; SOCKET test_socket = ::socket(AF_INET, SOCK_DGRAM, 0); if (test_socket == INVALID_SOCKET) { if (::WSAGetLastError() == WSAEAFNOSUPPORT) { retcode = false; } else { WSA_ERROR; } } else { WSA_CHECK(::closesocket(test_socket)); } return retcode; } //------------------------------------------------------------------------------- void cNetUtil::Wsa_Init() { // // winsock 1.1 // WSADATA wsa_data; if (::WSAStartup(MAKEWORD(1, 1), &wsa_data) != 0) { DIE; } } //------------------------------------------------------------------------------- bool cNetUtil::Protocol_Init(bool is_internet) { IsInternet = is_internet; bool retcode = false; if (Is_Tcpip_Present()) { retcode = true; if (IsInternet) { DefaultResendTimeoutMs = RESEND_TIMEOUT_INTERNET_MS; } else { DefaultResendTimeoutMs = RESEND_TIMEOUT_LAN_MS; } } else { retcode = false; } return retcode; } /* //------------------------------------------------------------------------------- float cNetUtil::Compute_Priority_Noise() { // // Add some noise to increase the spread. This noise must be bigger // than the maximum threshold adjustment, so PriorityNoiseFactor should // be at least 1 if you are adding noise. // Do not jitter a priority of zero! // // Or maybe we can just specify a max noise now? // float noise_width = PriorityNoiseFactor * MaxTPCorrectionDownwards; return cMathUtil::Get_Hat_Pdf_Double(-noise_width / 2.0f, +noise_width / 2.0f); } */ //------------------------------------------------------------------------------- void cNetUtil::Set_Socket_Buffer_Sizes(SOCKET sock, int new_size) { WWDEBUG_SAY(("cNetUtil::Set_Socket_Buffer_Sizes:\n")); int buffersize = 0; int len = 0; buffersize = 0; len = sizeof(int); WSA_CHECK(::getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize, &len)); //WWDEBUG_SAY((" SO_SNDBUF = %d\n", buffersize)); buffersize = 0; len = sizeof(int); WSA_CHECK(::getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize, &len)); //WWDEBUG_SAY((" SO_RCVBUF = %d\n", buffersize)); buffersize = new_size; len = sizeof(int); WSA_CHECK(setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize, len)); //WWDEBUG_SAY((" Attempting to set SO_SNDBUF = %d\n", buffersize)); buffersize = new_size; len = sizeof(int); WSA_CHECK(setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize, len)); //WWDEBUG_SAY((" Attempting to set SO_RCVBUF = %d\n", buffersize)); buffersize = 0; len = sizeof(int); WSA_CHECK(::getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize, &len)); //WWDEBUG_SAY((" SO_SNDBUF = %d\n", buffersize)); buffersize = 0; len = sizeof(int); WSA_CHECK(::getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize, &len)); //WWDEBUG_SAY((" SO_RCVBUF = %d\n", buffersize)); } /* //------------------------------------------------------------------------------- void cNetUtil::Onetime_Init() { FileClass * p_ini_file = _TheFileFactory->Get_File("netparams.ini"); if (p_ini_file != NULL) { INIClass netparams_ini(*p_ini_file); WWASSERT(netparams_ini.Section_Count() == 1); const LPCSTR SECTION_NAME = "Settings"; //NETSTATS_SAMPLE_TIME_MS = netparams_ini.Get_Int(SECTION_NAME, "NETSTATS_SAMPLE_TIME_MS", INVALID_VALUE); //WWASSERT(NETSTATS_SAMPLE_TIME_MS > 0); //RESEND_TIMEOUT_LAN_MS = netparams_ini.Get_Int(SECTION_NAME, "RESEND_TIMEOUT_LAN_MS", INVALID_VALUE); //WWASSERT(RESEND_TIMEOUT_LAN_MS > 0); //RESEND_TIMEOUT_INTERNET_MS = netparams_ini.Get_Int(SECTION_NAME, "RESEND_TIMEOUT_INTERNET_MS", INVALID_VALUE); //WWASSERT(RESEND_TIMEOUT_INTERNET_MS > 0); //DefaultMultiSends = netparams_ini.Get_Int(SECTION_NAME, "DefaultMultiSends", INVALID_VALUE); //WWASSERT(DefaultMultiSends > 0); //DefaultMaxResends = netparams_ini.Get_Int(SECTION_NAME, "DefaultMaxResends", INVALID_VALUE); //WWASSERT(DefaultMaxResends > 0); //DefaultKeepaliveTimeoutMs = netparams_ini.Get_Int(SECTION_NAME, "DefaultKeepaliveTimeoutMs", INVALID_VALUE); //WWASSERT(DefaultKeepaliveTimeoutMs > 0); //DesiredSendBufferSizeBytes = netparams_ini.Get_Int(SECTION_NAME, "DesiredSendBufferSizeBytes", INVALID_VALUE); //WWASSERT(DesiredSendBufferSizeBytes > 0); //DESIRED_RECEIVE_BUFFER_SIZE_BYTES = netparams_ini.Get_Int(SECTION_NAME, "DesiredReceiveBufferSizeBytes", INVALID_VALUE); //WWASSERT(DesiredReceiveBufferSizeBytes > 0); //DefaultServerPort = netparams_ini.Get_Int(SECTION_NAME, "DefaultServerPort", INVALID_VALUE); //WWASSERT(DefaultServerPort >= MIN_SERVER_PORT && DefaultServerPort <= MAX_SERVER_PORT); //MaxReceiveTimeMs = netparams_ini.Get_Int(SECTION_NAME, "MaxReceiveTimeMs", INVALID_VALUE); //WWASSERT(MaxReceiveTimeMs > 0); //PriorityToleranceDownwards = netparams_ini.Get_Float(SECTION_NAME, "PriorityToleranceDownwards", INVALID_VALUE); //WWASSERT(PriorityToleranceDownwards > -1 - MISCUTIL_EPSILON && PriorityToleranceDownwards < 1 + MISCUTIL_EPSILON); //PriorityToleranceUpwards = netparams_ini.Get_Float(SECTION_NAME, "PriorityToleranceUpwards", INVALID_VALUE); //WWASSERT(PriorityToleranceUpwards > -1 - MISCUTIL_EPSILON && PriorityToleranceUpwards < 1 + MISCUTIL_EPSILON); //MaxTPCorrectionDownwards = netparams_ini.Get_Float(SECTION_NAME, "MaxTPCorrectionDownwards", INVALID_VALUE); //WWASSERT(MaxTPCorrectionDownwards > -1 - MISCUTIL_EPSILON && MaxTPCorrectionDownwards < 1 + MISCUTIL_EPSILON); //MaxTPCorrectionUpwards = netparams_ini.Get_Float(SECTION_NAME, "MaxTPCorrectionUpwards", INVALID_VALUE); //WWASSERT(MaxTPCorrectionUpwards > -1 - MISCUTIL_EPSILON && MaxTPCorrectionUpwards < 1 + MISCUTIL_EPSILON); //PriorityNoiseFactor = netparams_ini.Get_Float(SECTION_NAME, "PriorityNoiseFactor", INVALID_VALUE); //WWASSERT(PriorityNoiseFactor >= -MISCUTIL_EPSILON); //InitialThresholdPriority = netparams_ini.Get_Float(SECTION_NAME, "InitialThresholdPriority", INVALID_VALUE); //WWASSERT(InitialThresholdPriority >= -MISCUTIL_EPSILON); //PriorityNoiseFactor = netparams_ini.Get_Float(SECTION_NAME, "PriorityNoiseFactor", INVALID_VALUE); //WWASSERT(PriorityNoiseFactor >= -MISCUTIL_EPSILON); //PriorityGrowthPerSecond = netparams_ini.Get_Float(SECTION_NAME, "PriorityGrowthPerSecond", INVALID_VALUE); //WWASSERT(PriorityGrowthPerSecond >= -MISCUTIL_EPSILON); _TheFileFactory->Return_File(p_ini_file); } } */ void cNetUtil::Create_Unbound_Socket(SOCKET & sock) { sock = ::socket(AF_INET, SOCK_DGRAM, 0); if (sock == INVALID_SOCKET) { WSA_ERROR; } // // Enable broadcasts // int optval = TRUE; WSA_CHECK(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *) &optval, sizeof(optval))); // // Make socket non-blocking // u_long arg = 1L; WSA_CHECK(ioctlsocket(sock, FIONBIO, (u_long *) &arg)); } //------------------------------------------------------------------------------- bool cNetUtil::Create_Bound_Socket(SOCKET & sock, USHORT port, SOCKADDR_IN & local_address) { // // TSS - is all this necessary or is above function OK? // Create_Unbound_Socket(sock); Create_Local_Address(&local_address, port); int result = ::bind(sock, (LPSOCKADDR) &local_address, sizeof(SOCKADDR_IN)); if (result == 0) { return true; } else { WWASSERT(result == SOCKET_ERROR); //if (::WSAGetLastError() != WSAEADDRINUSE) { WSA_ERROR; //} return false; } } //------------------------------------------------------------------------------- void cNetUtil::Close_Socket(SOCKET & sock) { ::closesocket(sock); } //----------------------------------------------------------------------------- void cNetUtil::Broadcast(SOCKET & sock, USHORT port, cPacket & packet) { SOCKADDR_IN broadcast_address; Create_Broadcast_Address(&broadcast_address, port); int bytes_sent; //WSA_CHECK(bytes_sent = sendto(sock, packet.Data, packet.SendLength, // 0, &broadcast_address, sizeof(SOCKADDR_IN))); bytes_sent = sendto(sock, packet.Get_Data(), packet.Get_Compressed_Size_Bytes(), 0, (LPSOCKADDR) &broadcast_address, sizeof(SOCKADDR_IN)); #pragma message("(TSS) WSAENOBUFS") //WWDEBUG_SAY(("Sent broadcast, length = %d bytes\n", bytes_sent)); } //------------------------------------------------------------------------------- void cNetUtil::Create_Broadcast_Address(LPSOCKADDR_IN p_broadcast_address, USHORT port) { WWASSERT(p_broadcast_address != NULL); ZeroMemory(p_broadcast_address, sizeof(SOCKADDR_IN)); p_broadcast_address->sin_family = AF_INET; p_broadcast_address->sin_addr.s_addr = INADDR_BROADCAST; // ::inet_addr("255.255.255.255"); p_broadcast_address->sin_port = ::htons(port); } //------------------------------------------------------------------------------- void cNetUtil::Create_Local_Address(LPSOCKADDR_IN p_local_address, USHORT port) { WWASSERT(p_local_address != NULL); ZeroMemory(p_local_address, sizeof(SOCKADDR_IN)); p_local_address->sin_family = AF_INET; p_local_address->sin_addr.s_addr = INADDR_ANY; p_local_address->sin_port = ::htons(port); } //------------------------------------------------------------------------------- bool cNetUtil::Get_Local_Address(LPSOCKADDR_IN p_local_address) { WWASSERT(p_local_address != NULL); /* const USHORT MAX_ADDRESSES = 1; int num_addresses = Get_Local_Tcpip_Addresses(p_local_address, MAX_ADDRESSES); return (num_addresses == 1); */ const USHORT MAX_ADDRESSES = 10; SOCKADDR_IN local_address[MAX_ADDRESSES]; int num_addresses = Get_Local_Tcpip_Addresses(local_address, MAX_ADDRESSES); if (num_addresses > 0) { ::memcpy(p_local_address, &local_address[0], sizeof(SOCKADDR_IN)); } return (num_addresses > 0); } //----------------------------------------------------------------------------- void cNetUtil::Lan_Servicing(SOCKET & sock, LanPacketHandlerCallback p_callback) { int retcode; unsigned long start_time = TIMEGETTIME(); do { cPacket packet; int address_len = sizeof(SOCKADDR_IN); // // If we appear to crash INSIDE recvfrom then this tends to indicate // that net neighbourhood broke. // retcode = recvfrom(sock, packet.Get_Data(), packet.Get_Max_Size(), 0, (LPSOCKADDR) &packet.Get_From_Address_Wrapper()->FromAddress, &address_len); if (retcode == SOCKET_ERROR) { if (::WSAGetLastError() != WSAEWOULDBLOCK) { WSA_ERROR; } } else { /* // // diagnostic // ULONG ip = packet.Get_From_Address_Wrapper()->FromAddress.sin_addr.s_addr; WWDEBUG_SAY(("cNetUtil::Lan_Servicing: %s\n", cNetUtil::Address_To_String(ip))); */ //packet.Set_Received_Length(retcode); packet.Set_Bit_Length(retcode * 8); (*p_callback)(packet); } } while (retcode != SOCKET_ERROR); // this will indicate no more data unsigned long time_spent = TIMEGETTIME() - start_time; if (time_spent > 100) { WWDEBUG_SAY(("*** cNetUtil::Lan_Servicing: Too much time (%d ms)) spent receiving lan packets.\n", time_spent)); } }