/* ** 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: rhost.cpp // Project: wwnet // Author: Tom Spencer-Smith // Date: June 1998 // Description: Data about a remote host // //----------------------------------------------------------------------------- #include "rhost.h" // I WANNA BE FIRST! #include #include #include "systimer.h" #include "miscutil.h" #include "mathutil.h" #include "connect.h" #include "wwdebug.h" #include "packetmgr.h" bool cRemoteHost::AllowExtraModemBandwidthThrottling = true; int cRemoteHost::PriorityUpdateRate = 15; // // class defines // //----------------------------------------------------------------------------- cRemoteHost::cRemoteHost() : ReliablePacketSendId(0), UnreliablePacketSendId(0), ReliablePacketRcvId(0), UnreliablePacketRcvId(0), LastKeepaliveTimeMs(TIMEGETTIME()), IsFlowControlEnabled(cConnection::Is_Flow_Control_Enabled()), MustEvict(false), LastReliableSendId(-2), // dummy value LastUnreliableSendId(-2), // dummy value ResendTimeoutMs(cNetUtil::Get_Default_Resend_Timeout_Ms()), LastServiceCount(0), LastContactTime(0), TargetBps(0), MaximumBps(0), MaxInternalPingtimeMs(0), AverageInternalPingtimeMs(0), BandwidthMultiplier(1.0f), AverageObjectPriority(0.5f), IsLoading(false), ExpectPacketFlood(false), WasLoading(0), CreationTime(TIMEGETTIME()), TotalResends(0), PriorityUpdateCounter(0), ExtendedAveragePingTime(0), ExtendedAverageCount(0), LastAveragePingTime(0), IsOutgoingFlooded(false), TotalResentPacketsInQueue(0), NextOutgoingFloodActionTime(0), NumOutgoingFloods(0) { //WWDEBUG_SAY(("cRemoteHost::cRemoteHost\n")); ZeroMemory(&Address, sizeof(SOCKADDR_IN)); if (IsFlowControlEnabled) { //ThresholdPriority = cNetUtil::Get_Initial_Threshold_Priority(); //ThresholdPriority = 1.0; ThresholdPriority = 0.5; } else { ThresholdPriority = 0.0; } //ThresholdPriority = 0.0; TPIncrement = 0.01;//TSS2001d Init_Stats(); } //----------------------------------------------------------------------------- void cRemoteHost::Init_Stats() { Stats.Init_Net_Stats(); // // Possibly, pull back ResendTimeoutMs if we have had a chance to measure ping time yet. // Adjust_Resend_Timeout(); //if (MaxInternalPingtimeMs && AverageInternalPingtimeMs) { // int candidate_resend_timeout_ms = min(3 * AverageInternalPingtimeMs, // (int) (1.1 * MaxInternalPingtimeMs)); // if (candidate_resend_timeout_ms < ResendTimeoutMs) { // WWASSERT(candidate_resend_timeout_ms < 0xffff); // ResendTimeoutMs = candidate_resend_timeout_ms; // //WWDEBUG_SAY((">> ResendTimeoutMs for rhost %d = %d\n", Id, ResendTimeoutMs)); // } //} //WWDEBUG_SAY(("ResendTimeoutMs for rhost %d = %d\n", Id, (unsigned long)ResendTimeoutMs)); WWDEBUG_SAY(("ResendTimeoutMs for rhost = %d\n", (unsigned long)ResendTimeoutMs)); TotalInternalPingtimeMs = 0; NumInternalPings = 0; AverageInternalPingtimeMs = 0;//-1; MinInternalPingtimeMs = 1000000; MaxInternalPingtimeMs = 0; } //----------------------------------------------------------------------------- void cRemoteHost::Set_Last_Service_Count(int service_count) { WWASSERT(service_count >= 0); LastServiceCount = service_count; } //----------------------------------------------------------------------------- void cRemoteHost::Compute_List_Max(int list_type) { WWASSERT(list_type >= 0 && list_type < 4); ListMax[list_type] = PacketList[list_type].Get_Count(); } //----------------------------------------------------------------------------- int cRemoteHost::Get_List_Max(int list_type) { WWASSERT(list_type >= 0 && list_type < 4); return ListMax[list_type]; } //----------------------------------------------------------------------------- void cRemoteHost::Set_List_Processing_Time(int list_type, int processing_time_ms) { WWASSERT(list_type >= 0 && list_type < 4); ListProcessingTime[list_type] = processing_time_ms; } //----------------------------------------------------------------------------- int cRemoteHost::Get_List_Processing_Time(int list_type) { WWASSERT(list_type >= 0 && list_type < 4); return ListProcessingTime[list_type]; } //----------------------------------------------------------------------------- SOCKADDR_IN & cRemoteHost::Get_Address() { return Address; } //----------------------------------------------------------------------------- cRemoteHost::~cRemoteHost() { SLNode * objnode; cPacket * p_packet; for (int list_type = 0; list_type < 4; list_type++) { for (objnode = PacketList[list_type].Head(); objnode != NULL;) { p_packet = objnode->Data(); WWASSERT(p_packet != NULL); objnode = objnode->Next(); PacketList[list_type].Remove(p_packet); p_packet->Flush(); delete p_packet; } } } //------------------------------------------------------------------------------------ void cRemoteHost::Add_Packet(cPacket & packet, BYTE list_type) { WWASSERT( list_type == RELIABLE_SEND_LIST || list_type == RELIABLE_RCV_LIST || list_type == UNRELIABLE_SEND_LIST || list_type == UNRELIABLE_RCV_LIST); WWASSERT(packet.Get_Id() >= 0); if (list_type == RELIABLE_SEND_LIST) { // // Test to make sure that additions to send list are in sequence. // if (LastReliableSendId != -2) { WWASSERT(packet.Get_Id() == LastReliableSendId + 1); } LastReliableSendId = packet.Get_Id(); } else if (list_type == UNRELIABLE_SEND_LIST) { if (LastUnreliableSendId != -2) { WWASSERT(packet.Get_Id() == LastUnreliableSendId + 1); } LastUnreliableSendId = packet.Get_Id(); } cPacket * p_packet = new cPacket; WWASSERT(p_packet != NULL); *p_packet = packet; // copy data if (list_type == RELIABLE_SEND_LIST || list_type == UNRELIABLE_SEND_LIST) { PacketList[list_type].Add_Tail(p_packet); } else { // // Locate insertion point according to packet id // SLNode * objnode; cPacket * obj = NULL; for (objnode = PacketList[list_type].Head(); objnode; objnode = objnode->Next()) { obj = objnode->Data(); WWASSERT(obj != NULL); if (obj->Get_Id() > packet.Get_Id()) { break; } } // // Insert packet at designated point // TSS - this is inefficient: we have already parsed the // list once // if (objnode == NULL) { PacketList[list_type].Add_Tail(p_packet); } else { PacketList[list_type].Insert_Before(p_packet, obj); } } } //------------------------------------------------------------------------------------ void cRemoteHost::Remove_Packet(int packet_id, BYTE list_type) { WWASSERT(packet_id >= 0); WWASSERT( list_type == RELIABLE_SEND_LIST || list_type == RELIABLE_RCV_LIST || list_type == UNRELIABLE_SEND_LIST || list_type == UNRELIABLE_RCV_LIST); SLNode * objnode; for (objnode = PacketList[list_type].Head(); objnode != NULL;) { cPacket * p_packet = objnode->Data(); WWASSERT(p_packet != NULL); objnode = objnode->Next(); if (packet_id == p_packet->Get_Id()) { if (list_type == RELIABLE_SEND_LIST) {// && // Packets that require a resend shouldn't count towards ping time calculations. It may be being removed because // the ACK to the first send just came in and if we just resent it then the ping time will look really low so we get // biased towards a low resend timeout value on connections of variable quality. ST - 12/7/2001 12:48PM if (p_packet->Get_Resend_Count() == 0 || NumInternalPings == 0) { unsigned long time = TIMEGETTIME(); int ping_time = time - p_packet->Get_Send_Time(); if (p_packet->Get_Resend_Count() != 0) { WWASSERT(NumInternalPings == 0); // If we are not getting any timing info at all then we need to do something. Use the first send time. It's going // to make it big but that should cut down on the resends and let us get better timing info. if (NumInternalPings == 0) { ping_time = TIMEGETTIME() - p_packet->Get_First_Send_Time(); } } TotalInternalPingtimeMs += ping_time; NumInternalPings++; if (NumInternalPings > 0) { AverageInternalPingtimeMs = cMathUtil::Round(TotalInternalPingtimeMs / (double) NumInternalPings); } else { AverageInternalPingtimeMs = 0; } if (ping_time < MinInternalPingtimeMs) { MinInternalPingtimeMs = ping_time; } if (ping_time > MaxInternalPingtimeMs) { MaxInternalPingtimeMs = ping_time; #if (0) int candidate_resend_timeout_ms = (int) (MaxInternalPingtimeMs * 1.1); if (candidate_resend_timeout_ms > ResendTimeoutMs) { ResendTimeoutMs = candidate_resend_timeout_ms; //WWDEBUG_SAY((">> ResendTimeoutMs for rhost %d = %d\n", Id, ResendTimeoutMs)); } #endif //(0) } } } //if (list_type == RELIABLE_SEND_LIST) { //WWDEBUG_SAY(("Removing packet %d from RELIABLE_SEND_LIST\n", packet_id)); //} PacketList[list_type].Remove(p_packet); p_packet->Flush(); delete p_packet; break; } } } //------------------------------------------------------------------------------------ void cRemoteHost::Toggle_Flow_Control() { IsFlowControlEnabled = !IsFlowControlEnabled; if (IsFlowControlEnabled) { //ThresholdPriority = 1.0; ThresholdPriority = 0.5; } else { ThresholdPriority = 0.0; } //ThresholdPriority = 0.0; } //------------------------------------------------------------------------------------ void cRemoteHost::Adjust_Flow_If_Necessary(float sample_time_ms) { if (!IsFlowControlEnabled) { return; } #ifdef OBSOLETE WWASSERT(TargetBps > 0); float sample_target_bits = sample_time_ms / 1000.0f * TargetBps; WWASSERT(sample_target_bits > 0); //TSS101401 //float ratio = Stats.StatSnapshot[STAT_BitsSent] / sample_target_bits; float ratio = PacketManager.Get_Compressed_Bandwidth_Out(&Get_Address()) / sample_target_bits; if (ratio > MISCUTIL_EPSILON) { double last_tp = TPIncrement; if (ratio > 1) { TPIncrement = ::fabs(TPIncrement); } else { TPIncrement = -::fabs(TPIncrement); } if ((TPIncrement > 0 && last_tp > 0) || (TPIncrement < 0 && last_tp < 0)) { if (::fabs(TPIncrement) < 0.025) { TPIncrement *= 1.5; } } else { if (::fabs(TPIncrement) > 0.0001) { TPIncrement *= 0.5; } } double new_tp = ThresholdPriority + TPIncrement; if (new_tp < 0) { new_tp = 0; } else if (new_tp > 1) { new_tp = 1; } ThresholdPriority = new_tp; /* if (Id == 1) { WWDEBUG_SAY(("ratio = %-7d / %-7d = %5.2f, TPIncrement = %9.7f, ThresholdPriority = %9.7f\n", Stats.StatSnapshot[STAT_BitsSent], (int) sample_target_bits, ratio, TPIncrement, ThresholdPriority)); } /**/ } #endif //OBSOLETE // // Do a similar calculation for the new bandwidth per object per client distribution method. // float actual = (float) PacketManager.Get_Compressed_Bandwidth_Out(&Get_Address()); float desired = (float) TargetBps; //sample_time_ms / 1000.0f * TargetBps; //if (Id > 1) { //WWDEBUG_SAY(("actual = %f, desired = %f\n", actual, desired)); //} if (ExpectPacketFlood) { BandwidthMultiplier = 0.5f; //1.5f; if (TIMEGETTIME() - FloodTimer > 8000) { ExpectPacketFlood = false; } } else { // // If we are sending way more than we know the remote host can receive then we need to send much less. // if (actual > desired) { BandwidthMultiplier = (BandwidthMultiplier * (desired / actual)) * 0.85f; } else { // // Another case we have to trap is a low bandwidth connection that has been accidentally flooded by having several // frames where we send more stuff than usual. It could also be a remote host that has incorrectly reported his // downstream bandwidth and can't receive as much as we think we can send to him. Or it could simply be some temporary // condition at either end of the connection or anywhere in between. If this happens we have to cut way back on sends // so that the connection can catch up. // if (AllowExtraModemBandwidthThrottling) { if (IsOutgoingFlooded) { // // We recently detected a outbound flooding condition. If we are still flooding then we have to take further action. // if (Is_Outgoing_Flooded()) { Dam_The_Flood(); } else { IsOutgoingFlooded = false; } } else { // // See if we are flooding now. // if (Is_Outgoing_Flooded()) { IsOutgoingFlooded = true; // // Well, something is wrong. It's kind of hard to say exactly what the problem is so a remedy is only guesswork. // In the short term we can try reducing the BandwidthMultiplier to clamp down on non-guaranteed packets. If the // problem persists then we will have to reduce MaximumBps too. // NumOutgoingFloods++; NextOutgoingFloodActionTime = 0; Dam_The_Flood(); } } } if (BandwidthMultiplier < 20.0f) { if (actual < (desired * 0.7f)) { /* ** Give bandwidth a quicker boost */ BandwidthMultiplier += 0.5f * (1.0f - (actual / desired)); if (BandwidthMultiplier > 20.0f) { BandwidthMultiplier = 20.0f; } } else { if (actual < (desired * 0.95f)) { /* ** Gradually increase bandwidth usage until we are close to max utilization. */ BandwidthMultiplier += 0.1f * (1.0f - (actual / desired)); if (BandwidthMultiplier > 20.0f) { BandwidthMultiplier = 20.0f; } } } } } } } //------------------------------------------------------------------------------------ bool cRemoteHost::Is_Outgoing_Flooded(void) { // // Don't take action if the rhost is loading. // if (Was_Recently_Loading()) { return(false); } // // We can try to detect outgoing floods in two ways. // // // 1. Look for a spike in the ping times. If we are sending more than the remote host can receive then ping times will // quickly rise as each packet backs up at the receive end and thus gets ack'd later than usual. // if (ExtendedAverageCount) { // average ping time over the life of the connection to use as a base line. int average = (int)(ExtendedAveragePingTime / (unsigned)ExtendedAverageCount); if ((LastAveragePingTime > 1500 && MaximumBps < 100000) || LastAveragePingTime > 500 && LastAveragePingTime > average * 3) { // // Ping time is bigger than we expect. If we are still receiving stuff from this host then chances are we are // flooding him. // if (TIMEGETTIME() - LastContactTime < 1000) { if (!IsOutgoingFlooded) { WWDEBUG_SAY(("Detected abnormal ping times - assuming outbound connection to rhost %d is flooded\n", Id)); WWDEBUG_SAY(("Normal average ping time = %d, last average ping time = %d\n", average, LastAveragePingTime)); } return(true); } } } // // 2. An excessive number of packets in the out queue that are older than the average ping time. Since acks aren't // coming back we resend more and so exacerbate the problem. // int total_in_queue = PacketList[RELIABLE_SEND_LIST].Get_Count(); // Let's say that if more than 90% of the packets in the queue have been resent then there is a problem. if (total_in_queue > 20 && (TotalResentPacketsInQueue*10) > (total_in_queue*9)) { // // More resends than we expect. If we are still receiving stuff from this host then chances are we are // flooding him. // if (TIMEGETTIME() - LastContactTime < 1000) { WWDEBUG_SAY(("Detected abnormally high number of resends - assuming outbound connection to rhost %d is flooded\n", Id)); WWDEBUG_SAY(("Total packets in queue = %d, queue packets resent = %d\n", total_in_queue, TotalResentPacketsInQueue)); return(true); } } return(false); } //------------------------------------------------------------------------------------ void cRemoteHost::Dam_The_Flood(void) { WWASSERT(IsOutgoingFlooded); // // How long since we last took action? // bool action_time = (TIMEGETTIME() > NextOutgoingFloodActionTime) ? true : false; // // Try something else every now and then until things improve. // if (action_time) { // // Try reducing BandwidthMultiplier. This will be a temporary change and will be allowed to revert once the connection // recovers. // if (BandwidthMultiplier > 0.5f || NextOutgoingFloodActionTime == 0) { BandwidthMultiplier = BandwidthMultiplier / 2.0f; // // Give this a half second or so to take effect. // if (BandwidthMultiplier <= 0.5f) { // // Reducing MaximumBps is a last resort. Give it a little more time to recover. // NextOutgoingFloodActionTime = 2000 + TIMEGETTIME(); } else { NextOutgoingFloodActionTime = 600 + TIMEGETTIME(); } } else { // // Try reducing MaximumBps. This will be a permanent change. 30,000ish should be our minimum since the min requirement is // a 33600 modem. // if (MaximumBps > 30000) { #ifdef WWDEBUG int old_bps = MaximumBps; #endif //WWDEBUG if (MaximumBps > 100000) { MaximumBps = 100000; } else { if (MaximumBps > 56000) { MaximumBps = 56000; } else { //if (MaximumBps > 40000) { // MaximumBps = 40000; //} else { if (MaximumBps > 33600) { MaximumBps = 33600; } else { MaximumBps = 30000; } //} } } WWDEBUG_SAY(("Reduced MaximumBps for rhost %d from %d to %d\n", Id, old_bps, MaximumBps)); /* ** Give this a few seconds to take effect before stepping down again. */ NextOutgoingFloodActionTime = 5000 + TIMEGETTIME(); } } } } //------------------------------------------------------------------------------------ void cRemoteHost::Set_Flood(bool state) { ExpectPacketFlood = state; if (state) { FloodTimer = TIMEGETTIME(); } } //------------------------------------------------------------------------------------ void cRemoteHost::Set_Is_Loading(bool state) { if (IsLoading && !state) { WasLoading = TIMEGETTIME(); } IsLoading = state; } //------------------------------------------------------------------------------------ bool cRemoteHost::Was_Recently_Loading(unsigned long time) { if (IsLoading) { return(true); } if (time == 0) { time = TIMEGETTIME(); } if (time - WasLoading < 1000 * 8) { return(true); } return(false); } //------------------------------------------------------------------------------------ void cRemoteHost::Adjust_Resend_Timeout(void) { if (NumInternalPings > 0) { if (MaxInternalPingtimeMs && AverageInternalPingtimeMs) { int candidate_resend_timeout_ms = min(3 * AverageInternalPingtimeMs, (int) (1.3f * (float)MaxInternalPingtimeMs)); // Make sure it stays inside a reasonable range. candidate_resend_timeout_ms = min(candidate_resend_timeout_ms, 3000); candidate_resend_timeout_ms = max(candidate_resend_timeout_ms, 333); ResendTimeoutMs = (ResendTimeoutMs + candidate_resend_timeout_ms) / 2; if (candidate_resend_timeout_ms < ResendTimeoutMs) { WWASSERT(candidate_resend_timeout_ms < 0xffff); ResendTimeoutMs = candidate_resend_timeout_ms; //WWDEBUG_SAY((">> ResendTimeoutMs for rhost %d = %d\n", Id, ResendTimeoutMs)); } //WWDEBUG_SAY(("ResendTimeoutMs for rhost = %d\n", (unsigned long)ResendTimeoutMs)); // // Keep track of the last average we calculated plus the average ping over the life of the connection. // ExtendedAveragePingTime += (unsigned) TotalInternalPingtimeMs; ExtendedAverageCount++; LastAveragePingTime = AverageInternalPingtimeMs; } TotalInternalPingtimeMs = 0; NumInternalPings = 0; AverageInternalPingtimeMs = 0;//-1; MinInternalPingtimeMs = 65535; MaxInternalPingtimeMs = 0; } }