198 lines
6.9 KiB
C#
198 lines
6.9 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|