Editor/Connect/StunClient.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace TeamCreate;
public sealed class StunClient : IDisposable
{
private const uint MagicCookie = 554869826u;
private const int HeaderBytes = 20;
private const ushort BindingRequest = 1;
private const ushort BindingSuccess = 257;
private const ushort MappedAddress = 1;
private const ushort XorMappedAddress = 32;
private static readonly (string Host, int Port)[] Servers = new (string, int)[2]
{
("stun.l.google.com", 19302),
("stun1.l.google.com", 19302)
};
private static readonly TimeSpan OverallDeadline = TimeSpan.FromSeconds(2L);
private static readonly TimeSpan ServerBudget = TimeSpan.FromSeconds(1L);
private readonly object gate = new object();
private int index;
private bool started;
private bool disposed;
private DateTime startedAt;
private DateTime sentAt;
private Task<IPAddress[]> resolution;
private UdpClient socket;
private byte[] transactionId;
public string PublicEndPoint { get; private set; }
public string Failure { get; private set; }
public int ServerCount => Servers.Length;
public int TriedCount { get; private set; }
public void Poll()
{
if (PublicEndPoint != null || Failure != null || disposed)
{
return;
}
try
{
Step();
}
catch (Exception error)
{
Fail(CollaborationDiagnostics.DescribeExternalFailure("Public address lookup: ", error));
}
}
private void Step()
{
DateTime utcNow = DateTime.UtcNow;
if (!started)
{
started = true;
startedAt = utcNow;
}
if (utcNow - startedAt >= OverallDeadline || index >= Servers.Length)
{
Exhaust();
}
else if (resolution == null)
{
resolution = Dns.GetHostAddressesAsync(Servers[index].Host);
}
else
{
if (!resolution.IsCompleted)
{
return;
}
if (socket == null)
{
if (!Begin(utcNow))
{
Advance();
}
}
else if (!Receive() && utcNow - sentAt >= ServerBudget)
{
Advance();
}
}
}
private bool Begin(DateTime now)
{
IPAddress iPAddress = null;
try
{
IPAddress[] result = resolution.GetAwaiter().GetResult();
foreach (IPAddress iPAddress2 in result)
{
if (iPAddress2.AddressFamily == AddressFamily.InterNetwork)
{
iPAddress = iPAddress2;
break;
}
}
}
catch (Exception)
{
iPAddress = null;
}
resolution = null;
if (iPAddress == null)
{
return false;
}
byte[] array = BuildBindingRequest(out var array2);
UdpClient udpClient = new UdpClient();
udpClient.Connect(new IPEndPoint(iPAddress, Servers[index].Port));
udpClient.Send(array, array.Length);
transactionId = array2;
socket = udpClient;
sentAt = now;
TriedCount++;
return true;
}
private bool Receive()
{
try
{
if (socket == null || !socket.Client.Poll(0, SelectMode.SelectRead) || socket.Available == 0)
{
return false;
}
IPEndPoint remoteEP = null;
byte[] array = socket.Receive(ref remoteEP);
if (!HasTransactionId(array))
{
return false;
}
if (!TryReadMappedAddress(array, out var address, out var _))
{
return false;
}
PublicEndPoint = address;
return true;
}
catch (SocketException)
{
return false;
}
}
private bool HasTransactionId(byte[] bytes)
{
if (transactionId == null || bytes == null || bytes.Length < 20)
{
return false;
}
for (int i = 0; i < 12; i++)
{
if (bytes[8 + i] != transactionId[i])
{
return false;
}
}
return true;
}
private void Advance()
{
try
{
socket?.Close();
}
catch
{
}
socket = null;
resolution = null;
transactionId = null;
index++;
if (index >= Servers.Length)
{
Exhaust();
}
}
private void Exhaust()
{
if (PublicEndPoint == null)
{
Fail((TriedCount == 0) ? "The public address could not be determined: the STUN servers could not be resolved on this network." : "The public address could not be determined: no STUN server answered within two seconds (UDP may be blocked).");
}
}
private void Fail(string message)
{
lock (gate)
{
if (Failure == null)
{
string text = (Failure = CollaborationDiagnostics.Bounded(message));
}
}
try
{
socket?.Close();
}
catch
{
}
socket = null;
}
internal static byte[] BuildBindingRequest(out byte[] transactionId)
{
transactionId = RandomNumberGenerator.GetBytes(12);
byte[] array = new byte[20]
{
0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
uint num = 554869826u;
array[4] = (byte)(num >> 24);
array[5] = (byte)(num >> 16);
array[6] = (byte)(num >> 8);
array[7] = (byte)num;
Array.Copy(transactionId, 0, array, 8, 12);
return array;
}
internal static bool TryReadMappedAddress(byte[] response, out string address, out string error)
{
address = null;
error = null;
if (response == null || response.Length < 20)
{
error = "The datagram is shorter than a STUN header.";
return false;
}
if (ReadUInt16(response, 0) != 257)
{
error = "The datagram is not a STUN Binding Success Response.";
return false;
}
if (ReadUInt32(response, 4) != 554869826)
{
error = "The datagram does not carry the STUN magic cookie.";
return false;
}
ushort num = ReadUInt16(response, 2);
if (num != response.Length - 20)
{
error = "The STUN response declares a length its datagram does not have.";
return false;
}
int i = 20;
ushort num4;
for (int num2 = 20 + num; i + 4 <= num2; i += 4 + ((num4 + 3) & -4))
{
ushort num3 = ReadUInt16(response, i);
num4 = ReadUInt16(response, i + 2);
if (i + 4 + num4 > num2)
{
error = "The STUN response carries a truncated attribute.";
return false;
}
bool flag = ((num3 == 1 || num3 == 32) ? true : false);
if (flag && num4 >= 8 && response[i + 5] == 1)
{
byte[] array = new byte[4];
Array.Copy(response, i + 8, array, 0, 4);
if (num3 == 32)
{
uint num5 = 554869826u;
array[0] ^= (byte)(num5 >> 24);
array[1] ^= (byte)(num5 >> 16);
array[2] ^= (byte)(num5 >> 8);
array[3] ^= (byte)num5;
}
address = array[0] + "." + array[1] + "." + array[2] + "." + array[3];
return true;
}
}
error = "The STUN response carries no IPv4 mapped address.";
return false;
}
private static ushort ReadUInt16(byte[] bytes, int offset)
{
return (ushort)((bytes[offset] << 8) | bytes[offset + 1]);
}
private static uint ReadUInt32(byte[] bytes, int offset)
{
return (uint)((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]);
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
try
{
socket?.Close();
}
catch
{
}
socket = null;
}
}
}