Skip to content

Feature: Allow simulating outbound networking #579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions LiteNetLib/LiteNetLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(SimulateNetwork)'=='true'">
<DefineConstants>$(DefineConstants);SIMULATE_NETWORK</DefineConstants>
</PropertyGroup>

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);LITENETLIB_UNSAFE</DefineConstants>
Expand Down
43 changes: 38 additions & 5 deletions LiteNetLib/NetManager.Socket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,44 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd
message = expandedPacket.RawData;
}

#if DEBUG || SIMULATE_NETWORK
if (HandleSimulateOutboundPacketLoss())
{
if (expandedPacket != null)
PoolRecycle(expandedPacket);
return 0; // Simulate successful send to avoid triggering error handling
}

if (HandleSimulateOutboundLatency(message, start, length, remoteEndPoint))
{
if (expandedPacket != null)
PoolRecycle(expandedPacket);
return length; // Simulate successful send
}
#endif

return SendRawCoreWithCleanup(message, start, length, remoteEndPoint, expandedPacket);
}

private int SendRawCoreWithCleanup(byte[] message, int start, int length, IPEndPoint remoteEndPoint, NetPacket expandedPacket)
{
try
{
return SendRawCore(message, start, length, remoteEndPoint);
}
finally
{
if (expandedPacket != null)
PoolRecycle(expandedPacket);
}
}

// Core socket sending logic without simulation - used by both SendRaw and delayed packet processing
internal int SendRawCore(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
{
if (!_isRunning)
return 0;

var socket = _udpSocketv4;
if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support)
{
Expand Down Expand Up @@ -604,11 +642,6 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd
NetDebug.WriteError($"[S] {ex}");
return 0;
}
finally
{
if (expandedPacket != null)
PoolRecycle(expandedPacket);
}

if (result <= 0)
return 0;
Expand Down
99 changes: 88 additions & 11 deletions LiteNetLib/NetManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ private struct IncomingData
public DateTime TimeWhenGet;
}
private readonly List<IncomingData> _pingSimulationList = new List<IncomingData>();

private struct OutboundDelayedPacket
{
public byte[] Data;
public int Start;
public int Length;
public IPEndPoint EndPoint;
public DateTime TimeWhenSend;
}
private readonly List<OutboundDelayedPacket> _outboundSimulationList = new List<OutboundDelayedPacket>();

private readonly Random _randomGenerator = new Random();
private const int MinLatencyThreshold = 5;

Expand Down Expand Up @@ -193,12 +204,12 @@ private struct IncomingData
public int DisconnectTimeout = 5000;

/// <summary>
/// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode)
/// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)
/// </summary>
public bool SimulatePacketLoss = false;

/// <summary>
/// Simulate latency by holding packets for random time. (Works only in DEBUG mode)
/// Simulate latency by holding packets for random time. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)
/// </summary>
public bool SimulateLatency = false;

Expand All @@ -208,12 +219,12 @@ private struct IncomingData
public int SimulationPacketLossChance = 10;

/// <summary>
/// Minimum simulated latency (in milliseconds)
/// Minimum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value.
/// </summary>
public int SimulationMinLatency = 30;

/// <summary>
/// Maximum simulated latency (in milliseconds)
/// Maximum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value.
/// </summary>
public int SimulationMaxLatency = 100;

Expand Down Expand Up @@ -614,7 +625,7 @@ private void UpdateLogic()
stopwatch.Stop();
}

[Conditional("DEBUG")]
[Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
private void ProcessDelayedPackets()
{
if (!SimulateLatency)
Expand All @@ -634,6 +645,21 @@ private void ProcessDelayedPackets()
}
}
}

lock (_outboundSimulationList)
{
for (int i = 0; i < _outboundSimulationList.Count; i++)
{
var outboundData = _outboundSimulationList[i];
if (outboundData.TimeWhenSend <= time)
{
// Send the delayed packet directly to socket layer bypassing simulation
SendRawCore(outboundData.Data, outboundData.Start, outboundData.Length, outboundData.EndPoint);
_outboundSimulationList.RemoveAt(i);
i--;
}
}
}
}

private void ProcessNtpRequests(float elapsedMilliseconds)
Expand Down Expand Up @@ -816,32 +842,33 @@ private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
HandleMessageReceived(packet, remoteEndPoint);
}

[Conditional("DEBUG")]
[Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint)
{
if (!SimulateLatency)
{
return;
}

int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
if (latency > MinLatencyThreshold)
int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
int inboundLatency = roundTripLatency / 2;
if (inboundLatency > MinLatencyThreshold)
{
lock (_pingSimulationList)
{
_pingSimulationList.Add(new IncomingData
{
Data = packet,
EndPoint = remoteEndPoint,
TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency)
TimeWhenGet = DateTime.UtcNow.AddMilliseconds(inboundLatency)
});
}
// hold packet
_dropPacket = true;
}
}

[Conditional("DEBUG")]
[Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
private void HandleSimulatePacketLoss()
{
if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance)
Expand All @@ -850,6 +877,48 @@ private void HandleSimulatePacketLoss()
}
}

#if DEBUG || SIMULATE_NETWORK
private bool HandleSimulateOutboundLatency(byte[] data, int start, int length, IPEndPoint remoteEndPoint)
{
if (!SimulateLatency)
{
return false;
}

int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
int outboundLatency = roundTripLatency / 2;
if (outboundLatency > MinLatencyThreshold)
{
// Create a copy of the data to avoid issues with recycled packets
byte[] dataCopy = new byte[length];
Array.Copy(data, start, dataCopy, 0, length);

lock (_outboundSimulationList)
{
_outboundSimulationList.Add(new OutboundDelayedPacket
{
Data = dataCopy,
Start = 0,
Length = length,
EndPoint = remoteEndPoint,
TimeWhenSend = DateTime.UtcNow.AddMilliseconds(outboundLatency)
});
}

return true;
}
return false;
}
#endif

#if DEBUG || SIMULATE_NETWORK
private bool HandleSimulateOutboundPacketLoss()
{
bool shouldDrop = SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance;
return shouldDrop;
}
#endif

private void HandleMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
{
var originalPacketSize = packet.Size;
Expand Down Expand Up @@ -1637,19 +1706,27 @@ public void Stop(bool sendDisconnectMessages)
_lastPeerId = 0;

ClearPingSimulationList();
ClearOutboundSimulationList();

_connectedPeersCount = 0;
_pendingEventHead = null;
_pendingEventTail = null;
}

[Conditional("DEBUG")]
[Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
private void ClearPingSimulationList()
{
lock (_pingSimulationList)
_pingSimulationList.Clear();
}

[Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
private void ClearOutboundSimulationList()
{
lock (_outboundSimulationList)
_outboundSimulationList.Clear();
}

/// <summary>
/// Return peers count with connection state
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions docfx_project/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ server.Stop();
* (including library internal keepalive packets)
* default value: **5000 msec**.
* **SimulatePacketLoss**
* simulate packet loss by dropping random amout of packets. (Works only in DEBUG mode)
* simulate packet loss by dropping random amout of packets. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)
* default value: **false**
* **SimulateLatency**
* simulate latency by holding packets for random time. (Works only in DEBUG mode)
* simulate latency by holding packets for random time. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)
* default value: **false**
* **SimulationPacketLossChance**
* chance of packet loss when simulation enabled. value in percents.
Expand Down
4 changes: 2 additions & 2 deletions docs/api/LiteNetLib.NetManager.html
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ <h5 class="fieldValue">Field Value</h5>


<h4 id="LiteNetLib_NetManager_SimulateLatency" data-uid="LiteNetLib.NetManager.SimulateLatency">SimulateLatency</h4>
<div class="markdown level1 summary"><p>Simulate latency by holding packets for random time. (Works only in DEBUG mode)</p>
<div class="markdown level1 summary"><p>Simulate latency by holding packets for random time. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)</p>
</div>
<div class="markdown level1 conceptual"></div>
<h5 class="declaration">Declaration</h5>
Expand All @@ -611,7 +611,7 @@ <h5 class="fieldValue">Field Value</h5>


<h4 id="LiteNetLib_NetManager_SimulatePacketLoss" data-uid="LiteNetLib.NetManager.SimulatePacketLoss">SimulatePacketLoss</h4>
<div class="markdown level1 summary"><p>Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode)</p>
<div class="markdown level1 summary"><p>Simulate packet loss by dropping random amount of packets. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)</p>
</div>
<div class="markdown level1 conceptual"></div>
<h5 class="declaration">Declaration</h5>
Expand Down