using System.Net.Sockets; using System.Net; using System.Threading.Tasks; using System.Threading; using System; namespace Lobbies { /// /// Small udp server to receive udp packets and echo the data back to source /// 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); /// /// 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 /// 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); } /// /// Listen to requests and fire events /// /// The port to listen on 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; } } /// /// Listen to requests and fire events /// /// The port to listen on 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; } } /// /// Start udp listener /// /// The port to listen on 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(); } /// /// Stop udp listener /// 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; } } } }