LobbyServer/LobbyClient/UdpEchoServer.cs

211 lines
7.4 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);
}
public void Send(IPEndPoint remoteEndpoint, byte[] messageData, int messageLength)
{
if (!running || serverSocketV4 == null || serverSocketV6 == null)
throw new Exception("Listener not running!");
for (int i = 0; i < 16; i++)
if (remoteEndpoint.AddressFamily == AddressFamily.InterNetwork)
serverSocketV4.Send(messageData, messageLength, remoteEndpoint);
else
serverSocketV6.Send(messageData, messageLength, 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;
}
}
}
}