From 4b68b406df64d77891a30f2ce96816e9a322dec7 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Wed, 28 Aug 2024 16:16:47 -0700 Subject: [PATCH] Add a new jitter simulation that does "clumping" P4:9002451 --- include/steam/steamnetworkingtypes.h | 46 ++++- .../clientlib/csteamnetworkingsockets.cpp | 6 + .../steamnetworkingsockets_lowlevel.cpp | 175 +++++++++++------- .../steamnetworkingsockets_internal.h | 7 + 4 files changed, 168 insertions(+), 66 deletions(-) diff --git a/include/steam/steamnetworkingtypes.h b/include/steam/steamnetworkingtypes.h index 35ed361..efb945b 100644 --- a/include/steam/steamnetworkingtypes.h +++ b/include/steam/steamnetworkingtypes.h @@ -1402,15 +1402,53 @@ enum ESteamNetworkingConfigValue k_ESteamNetworkingConfig_FakePacketLag_Send = 4, k_ESteamNetworkingConfig_FakePacketLag_Recv = 5, - /// [global float] 0-100 Percentage of packets we will add additional delay - /// to (causing them to be reordered) + /// Simulated jitter/clumping. + /// + /// For each packet, a jitter value is determined (which may + /// be zero). This amount is added as extra delay to the + /// packet. When a subsequent packet is queued, it receives its + /// own random jitter amount from the current time. if this would + /// result in the packets being delivered out of order, the later + /// packet queue time is adjusted to happen after the first packet. + /// Thus simulating jitter by itself will not reorder packets, but it + /// can "clump" them. + /// + /// - Avg: A random jitter time is generated using an exponential + /// distribution using this value as the mean (ms). The default + /// is zero, which disables random jitter. + /// - Max: Limit the random jitter time to this value (ms). + /// - Pct: odds (0-100) that a random jitter value for the packet + /// will be generated. Otherwise, a jitter value of zero + /// is used, and the packet will only be delayed by the jitter + /// system if necessary to retain order, due to the jitter of a + /// previous packet. + /// + /// All values are [global float] + /// + /// Fake jitter is simulated after fake lag, but before reordering. + k_ESteamNetworkingConfig_FakePacketJitter_Send_Avg = 53, + k_ESteamNetworkingConfig_FakePacketJitter_Send_Max = 54, + k_ESteamNetworkingConfig_FakePacketJitter_Send_Pct = 55, + k_ESteamNetworkingConfig_FakePacketJitter_Recv_Avg = 56, + k_ESteamNetworkingConfig_FakePacketJitter_Recv_Max = 57, + k_ESteamNetworkingConfig_FakePacketJitter_Recv_Pct = 58, + + /// [global float] 0-100 Percentage of packets we will add additional + /// delay to. If other packet(s) are sent/received within this delay + /// window (that doesn't also randomly receive the same extra delay), + /// then the packets become reordered. + /// + /// This mechanism is primarily intended to generate out-of-order + /// packets. To simulate random jitter, use the FakePacketJitter. + /// Fake packet reordering is applied after fake lag and jitter k_ESteamNetworkingConfig_FakePacketReorder_Send = 6, k_ESteamNetworkingConfig_FakePacketReorder_Recv = 7, - /// [global int32] Extra delay, in ms, to apply to reordered packets. + /// [global int32] Extra delay, in ms, to apply to reordered + /// packets. The same time value is used for sending and receiving. k_ESteamNetworkingConfig_FakePacketReorder_Time = 8, - /// [global float 0--100] Globally duplicate some percentage of packets we send + /// [global float 0--100] Globally duplicate some percentage of packets. k_ESteamNetworkingConfig_FakePacketDup_Send = 26, k_ESteamNetworkingConfig_FakePacketDup_Recv = 27, diff --git a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp index 28f52a4..7560ac9 100644 --- a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp +++ b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp @@ -56,6 +56,12 @@ DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Send_Burst, 16*1024, 0, 1024*1024 DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Recv_Rate, 0, 0, 1024*1024*1024 ); DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Recv_Burst, 16*1024, 0, 1024*1024 ); DEFINE_GLOBAL_CONFIGVAL( int32, OutOfOrderCorrectionWindowMicroseconds, 1000, 0, 50*1000 ); +DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Send_Avg, 0.0f, 0.0f, 2000.0f ); +DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Send_Max, 100.0f, 0.0f, 5000.0f ); +DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Send_Pct, 75.0f, 0.0f, 100.0f ); +DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Recv_Avg, 0.0f, 0.0f, 2000.0f ); +DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Recv_Max, 100.0f, 0.0f, 5000.0f ); +DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Recv_Pct, 75.0f, 0.0f, 100.0f ); DEFINE_GLOBAL_CONFIGVAL( void *, Callback_AuthStatusChanged, nullptr ); #ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp index f1d2f07..a975a8a 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp @@ -1306,7 +1306,7 @@ class CPacketLagger : private IThinker public: ~CPacketLagger() { Clear(); } - void LagPacket( CRawUDPSocketImpl *pSock, const netadr_t &adr, int msDelay, int nChunks, const iovec *pChunks ) + void LagPacket( CRawUDPSocketImpl *pSock, const netadr_t &adr, SteamNetworkingMicroseconds usecTime, int nChunks, const iovec *pChunks ) { SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "LagPacket" ); @@ -1326,16 +1326,6 @@ public: return; } - if ( msDelay < 1 ) - { - AssertMsg( false, "Packet lag time must be positive!" ); - msDelay = 1; - } - - // Limit to something sane - msDelay = std::min( msDelay, 5000 ); - const SteamNetworkingMicroseconds usecTime = SteamNetworkingSockets_GetLocalTimestamp() + msDelay * 1000; - CLaggedPacket *pkt = new CLaggedPacket( pSock, adr, usecTime, cbPkt ); // Gather the payload data data into the buffer @@ -1759,6 +1749,29 @@ void WakeServiceThread() #endif } +inline SteamNetworkingMicroseconds RandomJitter( const GlobalConfigValue &ValAvg, const GlobalConfigValue &ValMax, const GlobalConfigValue &ValPct ) +{ + // The defaults disable jitter by setting the *average* to 0, so check that first. + if ( likely( ValAvg.Get() ) <= 0.0f ) + return 0; + if ( ValMax.Get() <= 0.0f ) + return 0; + if ( !RandomBoolWithOdds( ValPct.Get() ) ) + return 0; + + // Unscaled exponential distribution + float r = WeakRandomFloat( 0.000001f, 1.0f ); // log of 0 is undefined, set the minimum to a value that can be subtracted from 1. (Something close to FLT_EPSILON.) + float x = -logf( r ); + if ( !( x > 0.0f ) ) // Written "backwards" just in case math blows up + return 0; + + // Scale and clamp + float flJitterMS = std::min( x*ValAvg.Get(), ValMax.Get() ); + + // Convert to integer microseconds + return (SteamNetworkingMicroseconds)( flJitterMS * 1000.0f ); +} + bool CRawUDPSocketImpl::BSendRawPacketGather( int nChunks, const iovec *pChunks, const netadr_t &adrTo ) const { SteamNetworkingGlobalLock::AssertHeldByCurrentThread(); @@ -1797,28 +1810,47 @@ bool CRawUDPSocketImpl::BSendRawPacketGather( int nChunks, const iovec *pChunks, if ( RandomBoolWithOdds( GlobalConfig::FakePacketLoss_Send.Get() ) ) return true; - // Fake lag? - int32 nPacketFakeLagTotal = GlobalConfig::FakePacketLag_Send.Get(); - - // Check for simulating random packet reordering + // Read convars and decide if we're going to simulate any lag, jitter, reordering, or duplication + int msFakeLag = GlobalConfig::FakePacketLag_Send.Get(); + SteamNetworkingMicroseconds usecReorderLag = 0; if ( RandomBoolWithOdds( GlobalConfig::FakePacketReorder_Send.Get() ) ) - { - nPacketFakeLagTotal += GlobalConfig::FakePacketReorder_Time.Get(); - } + usecReorderLag = GlobalConfig::FakePacketReorder_Time.Get()*1000; + SteamNetworkingMicroseconds usecJitter = RandomJitter( GlobalConfig::FakePacketJitter_Send_Avg, GlobalConfig::FakePacketJitter_Send_Max, GlobalConfig::FakePacketJitter_Send_Pct ); + bool bDup = RandomBoolWithOdds( GlobalConfig::FakePacketDup_Send.Get() ); - // Check for simulating random packet duplication - if ( RandomBoolWithOdds( GlobalConfig::FakePacketDup_Send.Get() ) ) + // Anything active? + static SteamNetworkingMicroseconds s_usecMinNextJitteredTime; + if ( unlikely( msFakeLag > 0 || usecReorderLag > 0 || usecJitter > 0 || bDup || s_usecMinNextJitteredTime != 0 ) ) { - int32 nDupLag = nPacketFakeLagTotal + WeakRandomInt( 0, GlobalConfig::FakePacketDup_TimeMax.Get() ); - nDupLag = std::max( 1, nDupLag ); - s_packetLagQueueSend.LagPacket( const_cast( this ), adrTo, nDupLag, nChunks, pChunks ); - } + SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp(); + SteamNetworkingMicroseconds usecWhenProcess = usecNow + msFakeLag*1000; + usecJitter = std::max( usecJitter, s_usecMinNextJitteredTime - usecWhenProcess ); + if ( usecJitter > 0 ) + { + usecWhenProcess += usecJitter; + s_usecMinNextJitteredTime = usecWhenProcess + 1; + } + else + { + // End of clump, clear this so we can switch back to the + // fast path once options are turned off + s_usecMinNextJitteredTime = 0; + } + usecWhenProcess += usecReorderLag; - // Lag the original packet? - if ( nPacketFakeLagTotal > 0 ) - { - s_packetLagQueueSend.LagPacket( const_cast( this ), adrTo, nPacketFakeLagTotal, nChunks, pChunks ); - return true; + // Check for simulating random packet duplication + if ( bDup ) + { + SteamNetworkingMicroseconds usecDupLag = 1 + (SteamNetworkingMicroseconds)WeakRandomFloat( 0.0f, GlobalConfig::FakePacketDup_TimeMax.Get()*1000.0f ); + s_packetLagQueueSend.LagPacket( const_cast( this ), adrTo, usecWhenProcess + usecDupLag, nChunks, pChunks ); + } + + // Lag the original packet? + if ( usecWhenProcess > usecNow ) + { + s_packetLagQueueSend.LagPacket( const_cast( this ), adrTo, usecWhenProcess, nChunks, pChunks ); + return true; + } } // Now really send it @@ -2346,43 +2378,62 @@ static bool DrainSocket( CRawUDPSocketImpl *pSock ) pSock->TracePkt( false, info.m_adrFrom, 1, &tmp ); } - int32 nPacketFakeLagTotal = GlobalConfig::FakePacketLag_Recv.Get(); - - // Check for simulating random packet reordering + // Read convars and decide if we're going to simulate any lag, jitter, reordering, or duplication + int msFakeLag = GlobalConfig::FakePacketLag_Recv.Get(); + SteamNetworkingMicroseconds usecReorderLag = 0; if ( RandomBoolWithOdds( GlobalConfig::FakePacketReorder_Recv.Get() ) ) + usecReorderLag = GlobalConfig::FakePacketReorder_Time.Get()*1000; + SteamNetworkingMicroseconds usecJitter = RandomJitter( GlobalConfig::FakePacketJitter_Recv_Avg, GlobalConfig::FakePacketJitter_Recv_Max, GlobalConfig::FakePacketJitter_Recv_Pct ); + bool bDup = RandomBoolWithOdds( GlobalConfig::FakePacketDup_Recv.Get() ); + + // Anything active? + static SteamNetworkingMicroseconds s_usecMinNextJitteredTime; + if ( unlikely( msFakeLag > 0 || usecReorderLag > 0 || usecJitter > 0 || bDup || s_usecMinNextJitteredTime != 0 ) ) { - nPacketFakeLagTotal += GlobalConfig::FakePacketReorder_Time.Get(); + SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp(); + SteamNetworkingMicroseconds usecWhenProcess = usecNow + msFakeLag*1000; + usecJitter = std::max( usecJitter, s_usecMinNextJitteredTime - usecWhenProcess ); + if ( usecJitter > 0 ) + { + usecWhenProcess += usecJitter; + s_usecMinNextJitteredTime = usecWhenProcess + 1; + } + else + { + // End of clump, clear this so we can switch back to the + // fast path once options are turned off + s_usecMinNextJitteredTime = 0; + } + usecWhenProcess += usecReorderLag; + + // Check for simulating random packet duplication + if ( bDup ) + { + SteamNetworkingMicroseconds usecDupLag = 1 + (SteamNetworkingMicroseconds)WeakRandomFloat( 0.0f, GlobalConfig::FakePacketDup_TimeMax.Get()*1000.0f ); + iovec temp; + temp.iov_len = ret; + temp.iov_base = buf; + s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, usecWhenProcess + usecDupLag, 1, &temp ); + } + + // Lag the original packet? + if ( usecWhenProcess > usecNow ) + { + iovec temp; + temp.iov_len = ret; + temp.iov_base = buf; + s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, usecWhenProcess, 1, &temp ); + continue; + } } - // Check for simulating random packet duplication - if ( RandomBoolWithOdds( GlobalConfig::FakePacketDup_Recv.Get() ) ) - { - int32 nDupLag = nPacketFakeLagTotal + WeakRandomInt( 0, GlobalConfig::FakePacketDup_TimeMax.Get() ); - nDupLag = std::max( 1, nDupLag ); - iovec temp; - temp.iov_len = ret; - temp.iov_base = buf; - s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, nDupLag, 1, &temp ); - } - - // Check for simulating lag - if ( nPacketFakeLagTotal > 0 ) - { - iovec temp; - temp.iov_len = ret; - temp.iov_base = buf; - s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, nPacketFakeLagTotal, 1, &temp ); - } - else - { - - info.m_pPkt = buf; - info.m_cbPkt = ret; - info.m_usecNow = usecRecvFromEnd; - info.m_pSock = pSock; - info.m_bQueuedForOutOfOrder = false; - pSock->m_callback( info ); - } + // Process the packet now + info.m_pPkt = buf; + info.m_cbPkt = ret; + info.m_usecNow = usecRecvFromEnd; + info.m_pSock = pSock; + info.m_bQueuedForOutOfOrder = false; + pSock->m_callback( info ); #ifdef STEAMNETWORKINGSOCKETS_LOWLEVEL_TIME_SOCKET_CALLS SteamNetworkingMicroseconds usecProcessPacketEnd = SteamNetworkingSockets_GetLocalTimestamp(); diff --git a/src/steamnetworkingsockets/steamnetworkingsockets_internal.h b/src/steamnetworkingsockets/steamnetworkingsockets_internal.h index ec5df01..5a00904 100644 --- a/src/steamnetworkingsockets/steamnetworkingsockets_internal.h +++ b/src/steamnetworkingsockets/steamnetworkingsockets_internal.h @@ -927,6 +927,13 @@ namespace GlobalConfig extern GlobalConfigValue FakePacketReorder_Time; extern GlobalConfigValue FakePacketDup_Send; extern GlobalConfigValue FakePacketDup_Recv; + extern GlobalConfigValue FakePacketJitter_Send_Avg; + extern GlobalConfigValue FakePacketJitter_Send_Max; + extern GlobalConfigValue FakePacketJitter_Send_Pct; + extern GlobalConfigValue FakePacketJitter_Recv_Avg; + extern GlobalConfigValue FakePacketJitter_Recv_Max; + extern GlobalConfigValue FakePacketJitter_Recv_Pct; + extern GlobalConfigValue FakePacketDup_TimeMax; extern GlobalConfigValue PacketTraceMaxBytes; extern GlobalConfigValue FakeRateLimit_Send_Rate;