Direct connection support
This commit is contained in:
1 parent
a99b097a83
commit
fc25760c2f
12 files changed
+397
-40
No files matched your search
@@ -4,6 +4,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,6 +19,8 @@ namespace Lobbies
|
||||
|
||||
AutoResetEvent waitForExternalIp = new AutoResetEvent(false);
|
||||
|
||||
UdpEchoServer udpEchoServer = new UdpEchoServer();
|
||||
|
||||
private Dictionary<Guid, LobbyInfo> lobbyInformation = new Dictionary<Guid, LobbyInfo>();
|
||||
private string? host;
|
||||
private int port;
|
||||
@@ -75,6 +78,8 @@ namespace Lobbies
|
||||
|
||||
public void HostLobby(Guid gameId, string name, int gameMode, int maxPlayerCount, string? password, string? ip, int port)
|
||||
{
|
||||
udpEchoServer.Start(0);
|
||||
|
||||
byte[]? hash = null, salt = null;
|
||||
|
||||
if(!string.IsNullOrEmpty(password))
|
||||
@@ -91,8 +96,9 @@ namespace Lobbies
|
||||
PlayerCount = 0,
|
||||
PasswordHash = hash,
|
||||
PasswordSalt = salt,
|
||||
HostIp = ip,
|
||||
HostPort = port
|
||||
HostIps = GatherLocalIpAddresses().ToArray(),
|
||||
HostPort = port,
|
||||
HostTryPort = udpEchoServer.Port
|
||||
};
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
@@ -142,7 +148,7 @@ namespace Lobbies
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateLobby(string name, int gameMode, int maxPlayerCount, int playerCount, string? password, string? ip, int port)
|
||||
public void UpdateLobby(string name, int gameMode, int maxPlayerCount, int playerCount, string? password, int port)
|
||||
{
|
||||
byte[]? hash = null, salt = null;
|
||||
|
||||
@@ -159,8 +165,9 @@ namespace Lobbies
|
||||
PlayerCount = playerCount,
|
||||
PasswordHash = hash,
|
||||
PasswordSalt = salt,
|
||||
HostIp = ip,
|
||||
HostPort = port
|
||||
HostIps = GatherLocalIpAddresses().ToArray(),
|
||||
HostPort = port,
|
||||
HostTryPort = udpEchoServer.Port
|
||||
};
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
@@ -170,6 +177,8 @@ namespace Lobbies
|
||||
|
||||
public void CloseLobby()
|
||||
{
|
||||
udpEchoServer.Stop();
|
||||
|
||||
var lobbyDelete = new LobbyDelete()
|
||||
{
|
||||
|
||||
@@ -207,6 +216,32 @@ namespace Lobbies
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public async Task<IPAddress?> TryDirectConnection(IPAddress[] ipAddressesToTry, int tryPort)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
IPAddress? ret = null;
|
||||
using (var udpEchoClient = new UdpEchoServer())
|
||||
{
|
||||
udpEchoClient.Reached += (ep) =>
|
||||
{
|
||||
ret = ep.Address;
|
||||
};
|
||||
|
||||
udpEchoClient.Start(0);
|
||||
|
||||
foreach (var ip in ipAddressesToTry)
|
||||
{
|
||||
udpEchoClient.CheckConnectionPossible(new IPEndPoint(ip, tryPort));
|
||||
}
|
||||
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
public static IPAddress[] GetIPsByName(string hostName, bool ip4Wanted, bool ip6Wanted)
|
||||
{
|
||||
// Check if the hostname is already an IPAddress
|
||||
@@ -214,7 +249,7 @@ namespace Lobbies
|
||||
if (IPAddress.TryParse(hostName, out outIpAddress) == true)
|
||||
return new IPAddress[] { outIpAddress };
|
||||
//<----------
|
||||
|
||||
|
||||
IPAddress[] addresslist = Dns.GetHostAddresses(hostName);
|
||||
|
||||
if (addresslist == null || addresslist.Length == 0)
|
||||
@@ -361,10 +396,24 @@ namespace Lobbies
|
||||
catch { }
|
||||
}
|
||||
|
||||
public IEnumerable<IPAddress> GatherLocalIpAddresses()
|
||||
{
|
||||
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
|
||||
|
||||
foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses)
|
||||
{
|
||||
yield return addr.Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
waitForExternalIp.Dispose();
|
||||
tcpClient.Dispose();
|
||||
udpEchoServer.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
|
||||
namespace Lobbies
|
||||
{
|
||||
/// <summary>
|
||||
/// Small udp server to receive udp packets and echo the data back to source
|
||||
/// </summary>
|
||||
internal class UdpEchoServer : IDisposable
|
||||
{
|
||||
public const int SIO_UDP_CONNRESET = -1744830452;
|
||||
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
private bool running = false;
|
||||
private bool isDisposed = false;
|
||||
private UdpClient? serverSocketV4, serverSocketV6;
|
||||
|
||||
public delegate void ReachableEventArgs(IPEndPoint remoteEndpoint);
|
||||
/// <summary>
|
||||
/// If a valid request for a ip and port query comes in call this event with the seen remote ip and port for a lobby server client id
|
||||
/// </summary>
|
||||
public event ReachableEventArgs? Reached;
|
||||
|
||||
public int Port { get; private set; }
|
||||
private bool isRunningV4 = false, isRunningV6 = false;
|
||||
|
||||
public void CheckConnectionPossible(IPEndPoint remoteEndpoint)
|
||||
{
|
||||
if (!running || serverSocketV4 == null || serverSocketV6 == null)
|
||||
throw new Exception("Listener not running!");
|
||||
|
||||
byte[] magicRequest = new byte[] { 0 };
|
||||
for (int i = 0; i < 16; i++)
|
||||
if(remoteEndpoint.AddressFamily == AddressFamily.InterNetwork)
|
||||
serverSocketV4.Send(magicRequest, magicRequest.Length, remoteEndpoint);
|
||||
else
|
||||
serverSocketV6.Send(magicRequest, magicRequest.Length, remoteEndpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen to requests and fire events
|
||||
/// </summary>
|
||||
/// <param name="port">The port to listen on</param>
|
||||
private async void ListenV4(int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||
serverSocketV4 = new UdpClient(port, AddressFamily.InterNetwork);
|
||||
serverSocketV4.Client.IOControl(
|
||||
(IOControlCode)SIO_UDP_CONNRESET,
|
||||
new byte[] { 0, 0, 0, 0 },
|
||||
null
|
||||
);
|
||||
Port = ((IPEndPoint)serverSocketV4.Client.LocalEndPoint).Port;
|
||||
byte[] magicAnswer = new byte[] { 1 };
|
||||
using (cancellationTokenSource.Token.Register(() => { serverSocketV4.Close(); }))
|
||||
{
|
||||
isRunningV4 = true;
|
||||
while (running)
|
||||
{
|
||||
var receiveResult = await serverSocketV4.ReceiveAsync();
|
||||
if (receiveResult.Buffer.Length == 1)
|
||||
{
|
||||
if (receiveResult.Buffer[0] == 0)
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
serverSocketV4.Send(magicAnswer, magicAnswer.Length, receiveResult.RemoteEndPoint);
|
||||
}
|
||||
|
||||
if (receiveResult.Buffer[0] == 1)
|
||||
{
|
||||
Reached?.Invoke(receiveResult.RemoteEndPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch when (cancellationTokenSource.IsCancellationRequested || !running) //Cancel requested
|
||||
{
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
serverSocketV4?.Dispose();
|
||||
serverSocketV4 = null;
|
||||
running = false;
|
||||
isRunningV4 = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen to requests and fire events
|
||||
/// </summary>
|
||||
/// <param name="port">The port to listen on</param>
|
||||
private async void ListenV6(int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||
serverSocketV6 = new UdpClient(port, AddressFamily.InterNetworkV6);
|
||||
serverSocketV6.Client.IOControl(
|
||||
(IOControlCode)SIO_UDP_CONNRESET,
|
||||
new byte[] { 0, 0, 0, 0 },
|
||||
null
|
||||
);
|
||||
byte[] magicAnswer = new byte[] { 1 };
|
||||
using (cancellationTokenSource.Token.Register(() => { serverSocketV6.Close(); }))
|
||||
{
|
||||
isRunningV6 = true;
|
||||
while (running)
|
||||
{
|
||||
var receiveResult = await serverSocketV6.ReceiveAsync();
|
||||
if (receiveResult.Buffer.Length == 1)
|
||||
{
|
||||
if (receiveResult.Buffer[0] == 0)
|
||||
{
|
||||
Reached?.Invoke(receiveResult.RemoteEndPoint);
|
||||
}
|
||||
|
||||
if (receiveResult.Buffer[0] == 1)
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
serverSocketV6.Send(magicAnswer, magicAnswer.Length, receiveResult.RemoteEndPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch when (cancellationTokenSource.IsCancellationRequested || !running) //Cancel requested
|
||||
{
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
serverSocketV6?.Dispose();
|
||||
serverSocketV6 = null;
|
||||
running = false;
|
||||
isRunningV6 = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start udp listener
|
||||
/// </summary>
|
||||
/// <param name="port">The port to listen on</param>
|
||||
public void Start(int port)
|
||||
{
|
||||
isRunningV4 = false;
|
||||
isRunningV6 = false;
|
||||
running = true;
|
||||
_ = Task.Run(() => ListenV4(port));
|
||||
while (running && !isRunningV4)
|
||||
Thread.Yield();
|
||||
_ = Task.Run(() => ListenV6(Port));
|
||||
while (running && (!isRunningV4 || !isRunningV6))
|
||||
Thread.Yield();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop udp listener
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
running = false;
|
||||
cancellationTokenSource.Cancel();
|
||||
if (serverSocketV4 != null)
|
||||
serverSocketV4?.Close();
|
||||
if (serverSocketV6 != null)
|
||||
serverSocketV6?.Close();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
Stop();
|
||||
while (isRunningV4 || isRunningV6)
|
||||
Task.Yield();
|
||||
|
||||
cancellationTokenSource.Dispose();
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user