Skip to content

Commit

Permalink
Update P2P handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Rans4ckeR committed Dec 7, 2022
1 parent ce1e90e commit dd6f904
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 86 deletions.
18 changes: 11 additions & 7 deletions DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -1330,11 +1334,11 @@ private async ValueTask BroadcastPlayerP2PRequestAsync()
{
if (!p2pPorts.Any())
{
IEnumerable<ushort> p2pReservedPorts = NetworkHelper.GetFreeUdpPorts(Array.Empty<ushort>(), MAX_REMOTE_PLAYERS);
p2pPorts = NetworkHelper.GetFreeUdpPorts(Array.Empty<ushort>(), 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)
{
Expand All @@ -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();
}

Expand Down
6 changes: 3 additions & 3 deletions DXMainClient/DXGUI/Multiplayer/GameLobby/LANGameLobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down
15 changes: 9 additions & 6 deletions DXMainClient/DXGUI/Multiplayer/LANGameLoadingLobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -188,7 +188,7 @@ private async ValueTask ListenForClientsAsync(CancellationToken cancellationToke

private async ValueTask HandleClientConnectionAsync(LANPlayerInfo lpInfo, CancellationToken cancellationToken)
{
using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(1024);
using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(4096);

while (!cancellationToken.IsCancellationRequested)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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();
}

Expand All @@ -319,7 +322,7 @@ private async ValueTask HandleServerCommunicationAsync(CancellationToken cancell
if (!client.Connected)
return;

using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(1024);
using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(4096);

while (!cancellationToken.IsCancellationRequested)
{
Expand All @@ -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)
Expand Down
22 changes: 11 additions & 11 deletions DXMainClient/DXGUI/Multiplayer/LANLobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(bufferSize);
Memory<byte> buffer = memoryOwner.Memory[..bufferSize];
int bytes = encoding.GetBytes(message.AsSpan(), buffer.Span);
const int charSize = sizeof(char);
int bufferSize = message.Length * charSize;
using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(bufferSize);
Memory<byte> 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)
Expand Down Expand Up @@ -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)));
Expand Down
100 changes: 61 additions & 39 deletions DXMainClient/Domain/Multiplayer/CnCNet/UPNP/UPnPHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Text;
using System.Xml;
using ClientCore;
using Rampastring.Tools;

namespace DTAClient.Domain.Multiplayer.CnCNet;

Expand All @@ -37,9 +38,11 @@ internal static class UPnPHandler
var p2pPorts = new List<ushort>();
var p2pIpV6PortIds = new List<ushort>();
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();
Expand All @@ -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)
{
Expand All @@ -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);
}
Expand Down Expand Up @@ -291,7 +313,7 @@ private static async Task<InternetGatewayDevice> 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);
}
Expand Down
15 changes: 12 additions & 3 deletions DXMainClient/Domain/Multiplayer/CnCNet/V3LocalPlayerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,8 +65,8 @@ public async ValueTask StartConnectionAsync()
{
remotePlayerEndPoint = new IPEndPoint(IPAddress.Loopback, 0);

using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(128);
Memory<byte> buffer = memoryOwner.Memory[..128];
using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(MaximumPacketSize);
Memory<byte> buffer = memoryOwner.Memory[..MaximumPacketSize];
int receiveTimeout = GameStartReceiveTimeout;

#if DEBUG
Expand Down Expand Up @@ -101,6 +103,10 @@ public async ValueTask StartConnectionAsync()

return;
}
catch (ObjectDisposedException)
{
return;
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
return;
Expand Down Expand Up @@ -133,7 +139,7 @@ public async ValueTask SendDataAsync(ReadOnlyMemory<byte> 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);
Expand All @@ -152,6 +158,9 @@ public async ValueTask SendDataAsync(ReadOnlyMemory<byte> data)
#endif
OnRaiseConnectionCutEvent(EventArgs.Empty);
}
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
}
Expand Down
Loading

0 comments on commit dd6f904

Please sign in to comment.