From dd6f904c4dd63c40ad822d25089563153a30b515 Mon Sep 17 00:00:00 2001 From: Rans4ckeR Date: Wed, 7 Dec 2022 22:32:33 +0100 Subject: [PATCH] Update P2P handling --- .../Multiplayer/GameLobby/CnCNetGameLobby.cs | 18 ++-- .../Multiplayer/GameLobby/LANGameLobby.cs | 6 +- .../DXGUI/Multiplayer/LANGameLoadingLobby.cs | 15 +-- DXMainClient/DXGUI/Multiplayer/LANLobby.cs | 22 ++-- .../Multiplayer/CnCNet/UPNP/UPnPHandler.cs | 100 +++++++++++------- .../CnCNet/V3LocalPlayerConnection.cs | 15 ++- .../CnCNet/V3RemotePlayerConnection.cs | 20 +++- .../Domain/Multiplayer/LAN/LANPlayerInfo.cs | 17 +-- .../Domain/Multiplayer/NetworkHelper.cs | 36 +++++++ 9 files changed, 163 insertions(+), 86 deletions(-) diff --git a/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs b/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs index 1d42ad8fe..7a5b86170 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net; using System.Net.NetworkInformation; -using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -984,8 +983,13 @@ private void StartV3ConnectionListeners() { foreach (var (remotePlayerName, remotePorts, localPingResults, remotePingResults, _) in p2pPlayers.Where(q => q.RemotePingResults.Any() && q.Enabled)) { - IEnumerable<(IPAddress IpAddress, long CombinedPing)> combinedPingResults = localPingResults.Select(q => (q.RemoteIpAddress, q.Ping + remotePingResults.SingleOrDefault(r => r.RemoteIpAddress.Equals(q.RemoteIpAddress)).Ping)); - (IPAddress ipAddress, long combinedPing) = combinedPingResults.OrderBy(q => q.CombinedPing).ThenByDescending(q => q.IpAddress.AddressFamily is AddressFamily.InterNetworkV6).First(); + (IPAddress selectedRemoteIpAddress, long combinedPing) = localPingResults + .Where(q => q.RemoteIpAddress is not null && remotePingResults + .Where(r => r.RemoteIpAddress is not null) + .Select(r => r.RemoteIpAddress.AddressFamily) + .Contains(q.RemoteIpAddress.AddressFamily)) + .Select(q => (q.RemoteIpAddress, q.Ping + remotePingResults.Single(r => r.RemoteIpAddress.AddressFamily == q.RemoteIpAddress.AddressFamily).Ping)) + .MaxBy(q => q.RemoteIpAddress.AddressFamily); if (combinedPing < playerTunnels.Single(q => q.RemotePlayerName.Equals(remotePlayerName, StringComparison.OrdinalIgnoreCase)).CombinedPing) { @@ -1000,7 +1004,7 @@ private void StartV3ConnectionListeners() p2pLocalTunnelHandler.RaiseRemoteHostConnectedEvent += (_, _) => AddCallback(() => GameTunnelHandler_Connected_CallbackAsync().HandleTask()); p2pLocalTunnelHandler.RaiseRemoteHostConnectionFailedEvent += (_, _) => AddCallback(() => GameTunnelHandler_ConnectionFailed_CallbackAsync().HandleTask()); - p2pLocalTunnelHandler.SetUp(new(ipAddress, remotePort), localPort, gameLocalPlayerId, gameStartCancellationTokenSource.Token); + p2pLocalTunnelHandler.SetUp(new(selectedRemoteIpAddress, remotePort), localPort, gameLocalPlayerId, gameStartCancellationTokenSource.Token); p2pLocalTunnelHandler.ConnectToTunnel(); v3GameTunnelHandlers.Add(new(new() { remotePlayerName }, p2pLocalTunnelHandler)); p2pPlayerTunnels.Add(remotePlayerName); @@ -1330,11 +1334,11 @@ private async ValueTask BroadcastPlayerP2PRequestAsync() { if (!p2pPorts.Any()) { - IEnumerable p2pReservedPorts = NetworkHelper.GetFreeUdpPorts(Array.Empty(), MAX_REMOTE_PLAYERS); + p2pPorts = NetworkHelper.GetFreeUdpPorts(Array.Empty(), MAX_REMOTE_PLAYERS).ToList(); try { - (internetGatewayDevice, p2pPorts, p2pIpV6PortIds, publicIpV6Address, publicIpV4Address) = await UPnPHandler.SetupPortsAsync(internetGatewayDevice, p2pReservedPorts); + (internetGatewayDevice, p2pPorts, p2pIpV6PortIds, publicIpV6Address, publicIpV4Address) = await UPnPHandler.SetupPortsAsync(internetGatewayDevice, p2pPorts); } catch (Exception ex) { @@ -1345,7 +1349,7 @@ private async ValueTask BroadcastPlayerP2PRequestAsync() } } - if ((publicIpV4Address is not null || publicIpV6Address is not null) && p2pPorts.Any()) + if (publicIpV4Address is not null || publicIpV6Address is not null) await SendPlayerP2PRequestAsync(); } diff --git a/DXMainClient/DXGUI/Multiplayer/GameLobby/LANGameLobby.cs b/DXMainClient/DXGUI/Multiplayer/GameLobby/LANGameLobby.cs index c0f54f826..92bd585d4 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameLobby/LANGameLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameLobby/LANGameLobby.cs @@ -198,7 +198,7 @@ private async ValueTask ListenForClientsAsync(CancellationToken cancellationToke { listener = new Socket(SocketType.Stream, ProtocolType.Tcp); - listener.Bind(new IPEndPoint(IPAddress.IPv6Any, ProgramConstants.LAN_GAME_LOBBY_PORT)); + listener.Bind(new IPEndPoint(IPAddress.Any, ProgramConstants.LAN_GAME_LOBBY_PORT)); listener.Listen(); while (!cancellationToken.IsCancellationRequested) @@ -256,7 +256,7 @@ private async ValueTask HandleClientConnectionAsync(LANPlayerInfo lpInfo, Cancel try { message = memoryOwner.Memory[..1024]; - bytesRead = await lpInfo.TcpClient.ReceiveAsync(message, SocketFlags.None, cancellationToken); + bytesRead = await lpInfo.TcpClient.ReceiveAsync(message, cancellationToken); } catch (OperationCanceledException) { @@ -383,7 +383,7 @@ private async ValueTask HandleServerCommunicationAsync(CancellationToken cancell try { message = memoryOwner.Memory[..1024]; - bytesRead = await client.ReceiveAsync(message, SocketFlags.None, cancellationToken); + bytesRead = await client.ReceiveAsync(message, cancellationToken); } catch (OperationCanceledException) { diff --git a/DXMainClient/DXGUI/Multiplayer/LANGameLoadingLobby.cs b/DXMainClient/DXGUI/Multiplayer/LANGameLoadingLobby.cs index 465c64bec..99f13b1ba 100644 --- a/DXMainClient/DXGUI/Multiplayer/LANGameLoadingLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/LANGameLoadingLobby.cs @@ -156,7 +156,7 @@ public async ValueTask PostJoinAsync() private async ValueTask ListenForClientsAsync(CancellationToken cancellationToken) { listener = new Socket(SocketType.Stream, ProtocolType.Tcp); - listener.Bind(new IPEndPoint(IPAddress.IPv6Any, ProgramConstants.LAN_GAME_LOBBY_PORT)); + listener.Bind(new IPEndPoint(IPAddress.Any, ProgramConstants.LAN_GAME_LOBBY_PORT)); listener.Listen(); while (!cancellationToken.IsCancellationRequested) @@ -188,7 +188,7 @@ private async ValueTask ListenForClientsAsync(CancellationToken cancellationToke private async ValueTask HandleClientConnectionAsync(LANPlayerInfo lpInfo, CancellationToken cancellationToken) { - using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(1024); + using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(4096); while (!cancellationToken.IsCancellationRequested) { @@ -197,7 +197,7 @@ private async ValueTask HandleClientConnectionAsync(LANPlayerInfo lpInfo, Cancel try { - message = memoryOwner.Memory[..1024]; + message = memoryOwner.Memory[..4096]; bytesRead = await client.ReceiveAsync(message, SocketFlags.None, cancellationToken); } catch (OperationCanceledException) @@ -308,7 +308,10 @@ private void HandleClientMessage(string data, LANPlayerInfo lpInfo) private void CleanUpPlayer(LANPlayerInfo lpInfo) { lpInfo.MessageReceived -= LpInfo_MessageReceived; - lpInfo.TcpClient.Shutdown(SocketShutdown.Both); + + if (lpInfo.TcpClient.Connected) + lpInfo.TcpClient.Shutdown(SocketShutdown.Both); + lpInfo.TcpClient.Close(); } @@ -319,7 +322,7 @@ private async ValueTask HandleServerCommunicationAsync(CancellationToken cancell if (!client.Connected) return; - using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(1024); + using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(4096); while (!cancellationToken.IsCancellationRequested) { @@ -328,7 +331,7 @@ private async ValueTask HandleServerCommunicationAsync(CancellationToken cancell try { - message = memoryOwner.Memory[..1024]; + message = memoryOwner.Memory[..4096]; bytesRead = await client.ReceiveAsync(message, SocketFlags.None, cancellationToken); } catch (OperationCanceledException) diff --git a/DXMainClient/DXGUI/Multiplayer/LANLobby.cs b/DXMainClient/DXGUI/Multiplayer/LANLobby.cs index 0fc4e30db..85a22fc38 100644 --- a/DXMainClient/DXGUI/Multiplayer/LANLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/LANLobby.cs @@ -334,19 +334,19 @@ public async ValueTask OpenAsync() private async ValueTask SendMessageAsync(string message, CancellationToken cancellationToken) { - try - { - if (!initSuccess) - return; + if (!initSuccess) + return; - const int charSize = sizeof(char); - int bufferSize = message.Length * charSize; - using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(bufferSize); - Memory buffer = memoryOwner.Memory[..bufferSize]; - int bytes = encoding.GetBytes(message.AsSpan(), buffer.Span); + const int charSize = sizeof(char); + int bufferSize = message.Length * charSize; + using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(bufferSize); + Memory buffer = memoryOwner.Memory[..bufferSize]; + int bytes = encoding.GetBytes(message.AsSpan(), buffer.Span); - buffer = buffer[..bytes]; + buffer = buffer[..bytes]; + try + { await socket.SendToAsync(buffer, SocketFlags.None, endPoint, cancellationToken); } catch (OperationCanceledException) @@ -505,7 +505,7 @@ private async ValueTask JoinGameAsync() HostedLANGame hg = (HostedLANGame)lbGameList.Items[lbGameList.SelectedIndex].Tag; - if (hg.Game.InternalName.ToUpper() != localGame.ToUpper()) + if (!hg.Game.InternalName.Equals(localGame, StringComparison.OrdinalIgnoreCase)) { lbChatMessages.AddMessage( string.Format("The selected game is for {0}!".L10N("UI:Main:GameIsOfPurpose"), gameCollection.GetGameNameFromInternalName(hg.Game.InternalName))); diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/UPNP/UPnPHandler.cs b/DXMainClient/Domain/Multiplayer/CnCNet/UPNP/UPnPHandler.cs index eaab5ae17..35bfa8b60 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/UPNP/UPnPHandler.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/UPNP/UPnPHandler.cs @@ -14,6 +14,7 @@ using System.Text; using System.Xml; using ClientCore; +using Rampastring.Tools; namespace DTAClient.Domain.Multiplayer.CnCNet; @@ -37,9 +38,11 @@ internal static class UPnPHandler var p2pPorts = new List(); var p2pIpV6PortIds = new List(); IPAddress routerPublicIpV4Address = null; - bool? routerNatEnabled = null; + bool routerNatEnabled = false; bool natDetected = false; + Logger.Log("Starting P2P Setup."); + if (internetGatewayDevice is null) { var internetGatewayDevices = (await GetInternetGatewayDevicesAsync(cancellationToken)).ToList(); @@ -50,26 +53,38 @@ internal static class UPnPHandler if (internetGatewayDevice is not null) { + Logger.Log("Found NAT device."); + routerNatEnabled = await internetGatewayDevice.GetNatRsipStatusAsync(cancellationToken); routerPublicIpV4Address = await internetGatewayDevice.GetExternalIpV4AddressAsync(cancellationToken); } + if (routerPublicIpV4Address == null) + { + Logger.Log("Using IPV4 detection."); + + routerPublicIpV4Address = await NetworkHelper.DetectPublicIpV4Address(cancellationToken); + } + var publicIpAddresses = NetworkHelper.GetPublicIpAddresses().ToList(); IPAddress publicIpV4Address = publicIpAddresses.FirstOrDefault(q => q.AddressFamily is AddressFamily.InterNetwork); - if ((routerNatEnabled ?? false) || (publicIpV4Address is not null && routerPublicIpV4Address is not null && !publicIpV4Address.Equals(routerPublicIpV4Address))) + if (routerNatEnabled || (publicIpV4Address is not null && routerPublicIpV4Address is not null && !publicIpV4Address.Equals(routerPublicIpV4Address))) natDetected = true; publicIpV4Address ??= routerPublicIpV4Address; + if (publicIpV4Address is not null) + Logger.Log("Public IPV4 detected."); + var privateIpV4Addresses = NetworkHelper.GetPrivateIpAddresses().Where(q => q.AddressFamily is AddressFamily.InterNetwork).ToList(); - IPAddress privateIpV4Address = null; + IPAddress privateIpV4Address = privateIpV4Addresses.FirstOrDefault(); - try + if (natDetected && privateIpV4Address is not null && publicIpV4Address is not null) { - privateIpV4Address = privateIpV4Addresses.FirstOrDefault(); + Logger.Log("Using IPV4 port mapping."); - if (natDetected && privateIpV4Address is not null) + try { foreach (int p2PReservedPort in p2pReservedPorts) { @@ -78,54 +93,61 @@ internal static class UPnPHandler p2pReservedPorts = p2pPorts; } - } - catch (Exception ex) - { - ProgramConstants.LogException(ex, $"Could not open P2P IPV4 ports for {privateIpV4Address} -> {publicIpV4Address}."); + catch (Exception ex) + { + ProgramConstants.LogException(ex, $"Could not open P2P IPV4 ports for {privateIpV4Address} -> {publicIpV4Address}."); + } } - IPAddress publicIpV6Address = null; + IPAddress publicIpV6Address; - try + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var publicIpV6Addresses = NetworkHelper.GetWindowsPublicIpAddresses().Where(q => q.IpAddress.AddressFamily is AddressFamily.InterNetworkV6).ToList(); + var publicIpV6Addresses = NetworkHelper.GetWindowsPublicIpAddresses().Where(q => q.IpAddress.AddressFamily is AddressFamily.InterNetworkV6).ToList(); - (IPAddress IpAddress, PrefixOrigin PrefixOrigin, SuffixOrigin SuffixOrigin) foundPublicIpV6Address = publicIpV6Addresses - .FirstOrDefault(q => q.PrefixOrigin is PrefixOrigin.RouterAdvertisement && q.SuffixOrigin is SuffixOrigin.LinkLayerAddress); + (IPAddress IpAddress, PrefixOrigin PrefixOrigin, SuffixOrigin SuffixOrigin) foundPublicIpV6Address = publicIpV6Addresses + .FirstOrDefault(q => q.PrefixOrigin is PrefixOrigin.RouterAdvertisement && q.SuffixOrigin is SuffixOrigin.LinkLayerAddress); - if (foundPublicIpV6Address.IpAddress is null) - { - foundPublicIpV6Address = publicIpV6Addresses - .FirstOrDefault(q => q.PrefixOrigin is PrefixOrigin.Dhcp && q.SuffixOrigin is SuffixOrigin.OriginDhcp); - } - - publicIpV6Address = foundPublicIpV6Address.IpAddress; - } - else + if (foundPublicIpV6Address.IpAddress is null) { - publicIpV6Address = NetworkHelper.GetPublicIpAddresses() - .FirstOrDefault(q => q.AddressFamily is AddressFamily.InterNetworkV6); + foundPublicIpV6Address = publicIpV6Addresses + .FirstOrDefault(q => q.PrefixOrigin is PrefixOrigin.Dhcp && q.SuffixOrigin is SuffixOrigin.OriginDhcp); } - if (publicIpV6Address is not null && internetGatewayDevice is not null) - { - (bool firewallEnabled, bool inboundPinholeAllowed) = await internetGatewayDevice.GetIpV6FirewallStatusAsync(cancellationToken); + publicIpV6Address = foundPublicIpV6Address.IpAddress; + } + else + { + publicIpV6Address = NetworkHelper.GetPublicIpAddresses() + .FirstOrDefault(q => q.AddressFamily is AddressFamily.InterNetworkV6); + } + + if (publicIpV6Address is not null) + { + Logger.Log("Public IPV6 detected."); - if (firewallEnabled && inboundPinholeAllowed) + if (internetGatewayDevice is not null) + { + try { - foreach (int p2pReservedPort in p2pReservedPorts) + (bool firewallEnabled, bool inboundPinholeAllowed) = await internetGatewayDevice.GetIpV6FirewallStatusAsync(cancellationToken); + + if (firewallEnabled && inboundPinholeAllowed) { - p2pIpV6PortIds.Add(await internetGatewayDevice.OpenIpV6PortAsync(publicIpV6Address, (ushort)p2pReservedPort, cancellationToken)); + Logger.Log("Configuring IPV6 firewall."); + + foreach (ushort p2pReservedPort in p2pReservedPorts) + { + p2pIpV6PortIds.Add(await internetGatewayDevice.OpenIpV6PortAsync(publicIpV6Address, p2pReservedPort, cancellationToken)); + } } } + catch (Exception ex) + { + ProgramConstants.LogException(ex, $"Could not open P2P IPV6 ports for {publicIpV6Address}."); + } } } - catch (Exception ex) - { - ProgramConstants.LogException(ex, $"Could not open P2P IPV6 ports for {publicIpV6Address}."); - } return (internetGatewayDevice, p2pPorts, p2pIpV6PortIds, publicIpV6Address, publicIpV4Address); } @@ -291,7 +313,7 @@ private static async Task GetInternetGatewayDeviceAsync(I { try { - location = locations.SingleOrDefault(q => q.HostNameType is UriHostNameType.IPv4); + location = locations.First(q => q.HostNameType is UriHostNameType.IPv4); uPnPDescription = await GetUPnPDescription(location, cancellationToken); } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/V3LocalPlayerConnection.cs b/DXMainClient/Domain/Multiplayer/CnCNet/V3LocalPlayerConnection.cs index 5a30582ec..f7d2c182f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/V3LocalPlayerConnection.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/V3LocalPlayerConnection.cs @@ -20,6 +20,8 @@ internal sealed class V3LocalPlayerConnection : IDisposable private const int SendTimeout = 10000; private const int GameStartReceiveTimeout = 60000; private const int ReceiveTimeout = 10000; + private const int MinimumPacketSize = 8; + private const int MaximumPacketSize = 1024; private Socket localGameSocket; private EndPoint remotePlayerEndPoint; @@ -63,8 +65,8 @@ public async ValueTask StartConnectionAsync() { remotePlayerEndPoint = new IPEndPoint(IPAddress.Loopback, 0); - using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(128); - Memory buffer = memoryOwner.Memory[..128]; + using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(MaximumPacketSize); + Memory buffer = memoryOwner.Memory[..MaximumPacketSize]; int receiveTimeout = GameStartReceiveTimeout; #if DEBUG @@ -101,6 +103,10 @@ public async ValueTask StartConnectionAsync() return; } + catch (ObjectDisposedException) + { + return; + } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { return; @@ -133,7 +139,7 @@ public async ValueTask SendDataAsync(ReadOnlyMemory data) Logger.Log($"Sending data from {localGameSocket.LocalEndPoint} to local game {remotePlayerEndPoint} for player {playerId}."); #endif - if (remotePlayerEndPoint is null) + if (remotePlayerEndPoint is null || data.Length < MinimumPacketSize) return; using var timeoutCancellationTokenSource = new CancellationTokenSource(SendTimeout); @@ -152,6 +158,9 @@ public async ValueTask SendDataAsync(ReadOnlyMemory data) #endif OnRaiseConnectionCutEvent(EventArgs.Empty); } + catch (ObjectDisposedException) + { + } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/V3RemotePlayerConnection.cs b/DXMainClient/Domain/Multiplayer/CnCNet/V3RemotePlayerConnection.cs index e8665833f..5290e82d8 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/V3RemotePlayerConnection.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/V3RemotePlayerConnection.cs @@ -17,6 +17,8 @@ internal sealed class V3RemotePlayerConnection : IDisposable private const int SendTimeout = 10000; private const int GameStartReceiveTimeout = 60000; private const int ReceiveTimeout = 60000; + private const int MinimumPacketSize = 8; + private const int MaximumPacketSize = 1024; private uint gameLocalPlayerId; private CancellationToken cancellationToken; @@ -67,8 +69,8 @@ public async ValueTask StartConnectionAsync() tunnelSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localPort)); - using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(50); - Memory buffer = memoryOwner.Memory[..50]; + using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(MaximumPacketSize); + Memory buffer = memoryOwner.Memory[..MaximumPacketSize]; if (!BitConverter.TryWriteBytes(buffer.Span[..4], gameLocalPlayerId)) throw new GameDataException(); @@ -156,6 +158,9 @@ public async ValueTask SendDataAsync(ReadOnlyMemory data, uint receiverId) #endif OnRaiseConnectionCutEvent(EventArgs.Empty); } + catch (ObjectDisposedException) + { + } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { } @@ -182,7 +187,7 @@ public void Dispose() private async ValueTask ReceiveLoopAsync() { - using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(4096); + using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(MaximumPacketSize); int receiveTimeout = GameStartReceiveTimeout; #if DEBUG @@ -193,7 +198,7 @@ private async ValueTask ReceiveLoopAsync() while (!cancellationToken.IsCancellationRequested) { - Memory buffer = memoryOwner.Memory[..1024]; + Memory buffer = memoryOwner.Memory[..MaximumPacketSize]; SocketReceiveFromResult socketReceiveFromResult; using var timeoutCancellationTokenSource = new CancellationTokenSource(receiveTimeout); using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationTokenSource.Token, cancellationToken); @@ -213,6 +218,10 @@ private async ValueTask ReceiveLoopAsync() return; } + catch (ObjectDisposedException) + { + return; + } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { return; @@ -231,7 +240,7 @@ private async ValueTask ReceiveLoopAsync() receiveTimeout = ReceiveTimeout; - if (socketReceiveFromResult.ReceivedBytes < 8) + if (socketReceiveFromResult.ReceivedBytes < MinimumPacketSize) { #if DEBUG Logger.Log($"Invalid data packet from {socketReceiveFromResult.RemoteEndPoint}"); @@ -256,6 +265,7 @@ private async ValueTask ReceiveLoopAsync() #else Logger.Log($"Invalid target (received: {receiverId}, expected: {gameLocalPlayerId}) on port {localPort}."); #endif + continue; } diff --git a/DXMainClient/Domain/Multiplayer/LAN/LANPlayerInfo.cs b/DXMainClient/Domain/Multiplayer/LAN/LANPlayerInfo.cs index 046d8e890..b5b793a57 100644 --- a/DXMainClient/Domain/Multiplayer/LAN/LANPlayerInfo.cs +++ b/DXMainClient/Domain/Multiplayer/LAN/LANPlayerInfo.cs @@ -97,7 +97,7 @@ public async ValueTask SendMessageAsync(string message, CancellationToken cancel try { - await TcpClient.SendAsync(buffer, SocketFlags.None, cancellationToken); + await TcpClient.SendAsync(buffer, cancellationToken); } catch (OperationCanceledException) { @@ -118,16 +118,16 @@ public override string ToString() /// public async ValueTask StartReceiveLoopAsync(CancellationToken cancellationToken) { - using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(1024); + using IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(4096); while (!cancellationToken.IsCancellationRequested) { int bytesRead; - Memory message = memoryOwner.Memory[..1024]; + Memory message = memoryOwner.Memory[..4096]; try { - bytesRead = await TcpClient.ReceiveAsync(message, SocketFlags.None, cancellationToken); + bytesRead = await TcpClient.ReceiveAsync(message, cancellationToken); } catch (OperationCanceledException) { @@ -147,8 +147,6 @@ public async ValueTask StartReceiveLoopAsync(CancellationToken cancellationToken msg = overMessage + msg; - var commands = new List(); - while (true) { int index = msg.IndexOf(ProgramConstants.LAN_MESSAGE_SEPARATOR); @@ -159,15 +157,10 @@ public async ValueTask StartReceiveLoopAsync(CancellationToken cancellationToken break; } - commands.Add(msg[..index]); + MessageReceived?.Invoke(this, new NetworkMessageEventArgs(msg[..index])); msg = msg[(index + 1)..]; } - foreach (string cmd in commands) - { - MessageReceived?.Invoke(this, new NetworkMessageEventArgs(cmd)); - } - continue; } diff --git a/DXMainClient/Domain/Multiplayer/NetworkHelper.cs b/DXMainClient/Domain/Multiplayer/NetworkHelper.cs index 8a9462a79..0d6585a1c 100644 --- a/DXMainClient/Domain/Multiplayer/NetworkHelper.cs +++ b/DXMainClient/Domain/Multiplayer/NetworkHelper.cs @@ -5,11 +5,16 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; namespace DTAClient.Domain.Multiplayer; internal static class NetworkHelper { + private const string PingHost = "cncnet.org"; + private const int PingTimeout = 10000; + private static readonly IReadOnlyCollection SupportedAddressFamilies = new[] { AddressFamily.InterNetwork, @@ -51,6 +56,37 @@ public static IPAddress GetIpV4BroadcastAddress(UnicastIPAddressInformation unic return new IPAddress(BitConverter.GetBytes(broadCastIpAddress)); } + public static async Task DetectPublicIpV4Address(CancellationToken cancellationToken) + { + IPAddress[] ipAddresses = await Dns.GetHostAddressesAsync(PingHost, cancellationToken).ConfigureAwait(false); + using var ping = new Ping(); + + foreach (IPAddress ipAddress in ipAddresses.Where(q => q.AddressFamily is AddressFamily.InterNetwork)) + { + PingReply pingReply = await ping.SendPingAsync(ipAddress, PingTimeout).ConfigureAwait(false); + + if (pingReply.Status is not IPStatus.Success) + continue; + + IPAddress pingIpAddress = null; + int ttl = 1; + + while (!ipAddress.Equals(pingIpAddress)) + { + pingReply = await ping.SendPingAsync(ipAddress, PingTimeout, Array.Empty(), new(ttl++, false)).ConfigureAwait(false); + pingIpAddress = pingReply.Address; + + if (ipAddress.Equals(pingIpAddress)) + break; + + if (!IsPrivateIpAddress(pingReply.Address)) + return pingReply.Address; + } + } + + return null; + } + /// /// Returns a free UDP port number above 1023. ///