From ead315d6933a0ffabe88fdcf1dd965c41b04e2ca Mon Sep 17 00:00:00 2001 From: Pandacoder <1476550+pandinocoder@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:02:52 +0200 Subject: [PATCH 1/5] Rider wants to track these :shrug: --- .idea/.idea.Intersect/.idea/vcs.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.idea/.idea.Intersect/.idea/vcs.xml b/.idea/.idea.Intersect/.idea/vcs.xml index 94a25f7f4c..2dc6e4d60d 100644 --- a/.idea/.idea.Intersect/.idea/vcs.xml +++ b/.idea/.idea.Intersect/.idea/vcs.xml @@ -2,5 +2,7 @@ + + \ No newline at end of file From c9c67ffff1b6e54abf8e49fd12d5c77169ec57c4 Mon Sep 17 00:00:00 2001 From: Pandacoder <1476550+pandinocoder@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:03:29 +0200 Subject: [PATCH 2/5] fix: remove blocking ICMP call and get disconnected ping from measuring server status round-trip --- .../Client/ServerStatusRequestPacket.cs | 4 +- .../Server/ServerStatusResponsePacket.cs | 4 +- .../MonoGame/Network/MonoSocket.cs | 126 ++++++++++++------ .../ServerStatusResponsePacketHandler.cs | 4 +- Intersect.Network/ClientNetwork.cs | 37 +---- .../ServerStatusRequestPacketHandler.cs | 5 +- 6 files changed, 103 insertions(+), 77 deletions(-) diff --git a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs index d1d5d061af..d4524f904e 100644 --- a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs +++ b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs @@ -13,4 +13,6 @@ public ServerStatusRequestPacket(byte[] responseKey) : base(responseKey) } [Key(2)] public byte[] VersionData { get; set; } -} \ No newline at end of file + + [Key(3)] public byte[] StateToken { get; set; } +} diff --git a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs index d383c3dece..0635b4c395 100644 --- a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs +++ b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs @@ -7,4 +7,6 @@ public partial class ServerStatusResponsePacket : UnconnectedResponsePacket { [Key(1)] public NetworkStatus Status { get; set; } -} \ No newline at end of file + + [Key(2)] public byte[] StateToken { get; set; } +} diff --git a/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs b/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs index 05321c6a03..4bc0d1125e 100644 --- a/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs +++ b/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs @@ -17,6 +17,7 @@ using Intersect.Core; using Intersect.Framework.Core; using Intersect.Network.Packets.Unconnected.Client; +using Intersect.Network.Packets.Unconnected.Server; using Intersect.Rsa; using Microsoft.Extensions.Logging; @@ -97,6 +98,9 @@ public override IClient Network private int? _lastPort; private IPEndPoint? _lastEndpoint; private volatile bool _resolvingHost; + private int _ping; + + private ServerStatusRequestMetadata? _serverStatusRequestMetadata; public static MonoSocket Instance { get; private set; } = default!; @@ -108,7 +112,7 @@ internal MonoSocket(IClientContext context) public override bool IsConnected => Network.IsConnected; - public override int Ping => Network.Ping; + public override int Ping => _ping < 0 ? Network.Ping : _ping; private bool TryResolveEndPoint([NotNullWhen(true)] out IPEndPoint? endPoint) { @@ -210,60 +214,80 @@ public override void Update() OnDataReceived(dequeued.Value); } - // ReSharper disable once InvertIf - if (Globals.GameState == GameStates.Menu) + switch (Globals.GameState) { - var now = Timing.Global.MillisecondsUtc; - // ReSharper disable once InvertIf - if (_nextServerStatusPing <= now || MainMenu.LastNetworkStatusChangeTime < 0) + case GameStates.InGame: + case GameStates.Error: + case GameStates.Intro: + case GameStates.Loading: + _ping = -1; + break; + + case GameStates.Menu: { - if (!_resolvingHost) + var now = Timing.Global.MillisecondsUtc; + // ReSharper disable once InvertIf + if (_nextServerStatusPing <= now || MainMenu.LastNetworkStatusChangeTime < 0) { - _resolvingHost = true; - Task.Run( - () => - { - try + if (!_resolvingHost) + { + _resolvingHost = true; + Task.Run( + () => { - if (TryResolveEndPoint(out var serverEndpoint)) + try { - var network = Network; - if (network == default) + if (TryResolveEndPoint(out var serverEndpoint)) { - ApplicationContext.Context.Value?.Logger.LogInformation("No network created to poll for server status."); + var network = Network; + if (network == default) + { + ApplicationContext.Context.Value?.Logger.LogInformation("No network created to poll for server status."); + } + else + { + ServerStatusRequestMetadata requestMetadata = new() + { + RequestTime = DateTime.UtcNow, + StateToken = Guid.NewGuid(), + }; + + network.SendUnconnected( + serverEndpoint, + new ServerStatusRequestPacket + { + StateToken = requestMetadata.StateToken.ToByteArray(), + VersionData = SharedConstants.VersionData, + } + ); + + _serverStatusRequestMetadata = requestMetadata; + } } - else + else if (!ClientNetwork.UnresolvableHostNames.Contains(_lastHost)) { - network.SendUnconnected( - serverEndpoint, - new ServerStatusRequestPacket - { - VersionData = SharedConstants.VersionData, - } - ); + ApplicationContext.Context.Value?.Logger.LogInformation($"Unable to resolve '{_lastHost}:{_lastPort}'"); } } - else if (!ClientNetwork.UnresolvableHostNames.Contains(_lastHost)) + catch (Exception exception) { - ApplicationContext.Context.Value?.Logger.LogInformation($"Unable to resolve '{_lastHost}:{_lastPort}'"); + ApplicationContext.Context.Value?.Logger.LogError(exception, "Error resolving host"); } + + _resolvingHost = false; } - catch (Exception exception) - { - ApplicationContext.Context.Value?.Logger.LogError(exception, "Error resolving host"); - } + ); + } - _resolvingHost = false; - } - ); - } + if (MainMenu.LastNetworkStatusChangeTime + (int)(ServerStatusPingInterval * 1.5f) < now) + { + MainMenu.SetNetworkStatus(NetworkStatus.Offline); + } - if (MainMenu.LastNetworkStatusChangeTime + (int)(ServerStatusPingInterval * 1.5f) < now) - { - MainMenu.SetNetworkStatus(NetworkStatus.Offline); + _nextServerStatusPing = now + ServerStatusPingInterval; } - _nextServerStatusPing = now + ServerStatusPingInterval; + break; } } } @@ -279,4 +303,30 @@ public override void Dispose() _network?.Dispose(); _network = default; } + + public void NotifyServerStatusResponse(ServerStatusResponsePacket responsePacket) + { + var now = DateTime.UtcNow; + + if (_serverStatusRequestMetadata is not {} requestMetadata) + { + return; + } + + Guid responseStateToken = new(responsePacket.StateToken); + if (requestMetadata.StateToken != responseStateToken) + { + return; + } + + var elapsed = now - requestMetadata.RequestTime; + _ping = (int)(elapsed.TotalMilliseconds * 0.5); + _serverStatusRequestMetadata = null; + } + + private record struct ServerStatusRequestMetadata + { + public DateTime RequestTime { get; init; } + public Guid StateToken { get; init; } + } } diff --git a/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs b/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs index f14ba05909..68b00fbca9 100644 --- a/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs +++ b/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs @@ -1,4 +1,5 @@ using Intersect.Client.Interface.Menu; +using Intersect.Client.MonoGame.Network; using Intersect.Core; using Intersect.Network; using Intersect.Network.Packets.Unconnected.Server; @@ -13,6 +14,7 @@ public override bool Handle(IPacketSender packetSender, ServerStatusResponsePack { try { + MonoSocket.Instance?.NotifyServerStatusResponse(packet); MainMenu.SetNetworkStatus(packet.Status); return true; } @@ -22,4 +24,4 @@ public override bool Handle(IPacketSender packetSender, ServerStatusResponsePack return false; } } -} \ No newline at end of file +} diff --git a/Intersect.Network/ClientNetwork.cs b/Intersect.Network/ClientNetwork.cs index 76ac7425da..1b43ce855f 100644 --- a/Intersect.Network/ClientNetwork.cs +++ b/Intersect.Network/ClientNetwork.cs @@ -72,41 +72,8 @@ public int Ping if (Configuration.Host is not { } hostNameOrAddress || UnresolvableHostNames.Contains(hostNameOrAddress)) { - return -1; - } - - try - { - // TODO: Add feature-specific log filtering, this one gets annoying - // ApplicationContext.Logger.LogTrace("Sending ping to server"); - - // Send a ping to the server. Timeout: 5000ms (5 seconds). Packet size: 32 bytes. TTL: 64. Don't fragment. - var reply = _ping.Send(hostNameOrAddress, 5000, [], new PingOptions(64, true)); - if (reply is { Status: IPStatus.Success }) - { - // Return the roundtrip time in milliseconds (ms) as an integer value (no decimals). - return (int)reply.RoundtripTime; - } - } - catch (PingException pingException) - { - if (pingException.InnerException is SocketException { SocketErrorCode: SocketError.HostNotFound }) - { - UnresolvableHostNames.Add(hostNameOrAddress); - ApplicationContext.Logger.LogWarning( - pingException, - "Invalid hostname '{HostNameOrAddress}' will not be pinged again", - hostNameOrAddress - ); - } - else - { - ApplicationContext.Logger.LogWarning(pingException, "Error sending ping request"); - } - } - catch (Exception exception) - { - ApplicationContext.Logger.LogWarning(exception, "Unknown error sending ping request"); + // Return a distinct ping value for unresolved host addresses so that it's clear why it's not showing + return -2; } return -1; diff --git a/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs b/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs index 7bb400770c..90f66e2deb 100644 --- a/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs +++ b/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs @@ -16,6 +16,7 @@ public override bool Handle(IPacketSender packetSender, ServerStatusRequestPacke { ResponseKey = packet.ResponseKey, Status = NetworkStatus.VersionMismatch, + StateToken = packet.StateToken, } ); } @@ -27,6 +28,7 @@ public override bool Handle(IPacketSender packetSender, ServerStatusRequestPacke { ResponseKey = packet.ResponseKey, Status = NetworkStatus.ServerFull, + StateToken = packet.StateToken, } ); } @@ -36,7 +38,8 @@ public override bool Handle(IPacketSender packetSender, ServerStatusRequestPacke { ResponseKey = packet.ResponseKey, Status = NetworkStatus.Online, + StateToken = packet.StateToken, } ); } -} \ No newline at end of file +} From 30a85c6a4dd8a9fbc9b056a8d023c40b8dfe464e Mon Sep 17 00:00:00 2001 From: Pandacoder <1476550+pandinocoder@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:20:30 +0200 Subject: [PATCH 3/5] Use RTT instead of one-way --- Intersect.Client.Core/MonoGame/Network/MonoSocket.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs b/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs index 4bc0d1125e..af97662154 100644 --- a/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs +++ b/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs @@ -320,7 +320,7 @@ public void NotifyServerStatusResponse(ServerStatusResponsePacket responsePacket } var elapsed = now - requestMetadata.RequestTime; - _ping = (int)(elapsed.TotalMilliseconds * 0.5); + _ping = (int)elapsed.TotalMilliseconds; _serverStatusRequestMetadata = null; } From c84877e6039621bc5d174e2dcddac44e2ba08cbb Mon Sep 17 00:00:00 2001 From: Pandacoder <1476550+pandinocoder@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:28:43 +0200 Subject: [PATCH 4/5] Actually update the ping even if it's 0 --- .../LiteNetLib/LiteNetLibInterface.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs b/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs index 860c05e46d..53a269ac27 100644 --- a/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs +++ b/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs @@ -483,14 +483,17 @@ public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketRead public void OnNetworkLatencyUpdate(NetPeer peer, int latency) { -#if !DIAGNOSTIC - if (latency < 1) - { - return; - } + +#if DIAGNOSTIC + var logLatencyUpdate = true; +#else + var logLatencyUpdate = latency > 0; #endif - ApplicationContext.CurrentContext.Logger.LogTrace("LATENCY {Peer} {Latency}ms", peer, latency); + if (logLatencyUpdate) + { + ApplicationContext.CurrentContext.Logger.LogTrace("LATENCY {Peer} {Latency}ms", peer, latency); + } if (!_connectionIdLookup.TryGetValue(peer.Id, out var connectionId)) { @@ -616,4 +619,4 @@ public void OnConnectionRequest(ConnectionRequest request) ApplicationContext.Context.Value?.Logger.LogDebug($"Approved {peer} ({connection.Guid})"); } -} \ No newline at end of file +} From bd4d2d4533a1cc0384776ff17efe957c2c3148cf Mon Sep 17 00:00:00 2001 From: Pandacoder <1476550+pandinocoder@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:29:35 +0200 Subject: [PATCH 5/5] Re-introduce short-circuit if the ping value is below 0 though (an error code) --- Intersect.Network/LiteNetLib/LiteNetLibInterface.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs b/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs index 53a269ac27..45268247c3 100644 --- a/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs +++ b/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs @@ -487,6 +487,11 @@ public void OnNetworkLatencyUpdate(NetPeer peer, int latency) #if DIAGNOSTIC var logLatencyUpdate = true; #else + if (latency < 0) + { + return; + } + var logLatencyUpdate = latency > 0; #endif