using STUN.Enums;
using STUN.Interfaces;
using STUN.Message;
using STUN.Proxy;
using STUN.StunResult;
using STUN.Utils;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
namespace STUN.Client
{
///
/// https://tools.ietf.org/html/rfc3489#section-10.1
/// https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
///
public class StunClient3489 : IDisposable
{
#region Subject
private readonly Subject _natTypeSubj = new Subject();
public IObservable NatTypeChanged => _natTypeSubj.AsObservable();
protected readonly Subject PubSubj = new Subject();
public IObservable PubChanged => PubSubj.AsObservable();
protected readonly Subject LocalSubj = new Subject();
public IObservable LocalChanged => LocalSubj.AsObservable();
#endregion
public IPEndPoint LocalEndPoint => Proxy.LocalEndPoint;
public TimeSpan Timeout
{
get => Proxy.Timeout;
set => Proxy.Timeout = value;
}
protected readonly IPAddress Server;
protected readonly ushort Port;
public IPEndPoint RemoteEndPoint => Server == null ? null : new IPEndPoint(Server, Port);
protected IUdpProxy Proxy;
public StunClient3489(string server, ushort port = 3478, IPEndPoint local = null, IUdpProxy proxy = null, IDnsQuery dnsQuery = null)
{
Proxy = proxy ?? new NoneUdpProxy(local, null);
Func dnsQuery1;
if (string.IsNullOrEmpty(server))
{
throw new ArgumentException(@"Please specify STUN server !");
}
if (port < 1)
{
throw new ArgumentException(@"Port value must be >= 1 !");
}
if (dnsQuery != null)
{
dnsQuery1 = dnsQuery.Query;
}
else
{
dnsQuery1 = new DefaultDnsQuery().Query;
}
Server = dnsQuery1(server);
if (Server == null)
{
throw new ArgumentException(@"Wrong STUN server !");
}
Port = port;
Timeout = TimeSpan.FromSeconds(1.6);
}
public async Task Query3489Async()
{
var res = new ClassicStunResult();
_natTypeSubj.OnNext(res.NatType);
PubSubj.OnNext(res.PublicEndPoint);
try
{
await Proxy.ConnectAsync();
// test I
var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
var (response1, remote1, local1) = await TestAsync(test1, RemoteEndPoint, RemoteEndPoint);
if (response1 == null)
{
res.NatType = NatType.UdpBlocked;
return res;
}
if (local1 != null)
{
LocalSubj.OnNext(LocalEndPoint);
}
var mappedAddress1 = AttributeExtensions.GetMappedAddressAttribute(response1);
var changedAddress1 = AttributeExtensions.GetChangedAddressAttribute(response1);
// 某些单 IP 服务器的迷惑操作
if (mappedAddress1 == null
|| changedAddress1 == null
|| Equals(changedAddress1.Address, remote1.Address)
|| changedAddress1.Port == remote1.Port)
{
res.NatType = NatType.UnsupportedServer;
return res;
}
PubSubj.OnNext(mappedAddress1); // 显示 test I 得到的映射地址
var test2 = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
};
// test II
var (response2, remote2, _) = await TestAsync(test2, RemoteEndPoint, changedAddress1);
var mappedAddress2 = AttributeExtensions.GetMappedAddressAttribute(response2);
if (Equals(mappedAddress1.Address, local1) && mappedAddress1.Port == LocalEndPoint.Port)
{
// No NAT
if (response2 == null)
{
res.NatType = NatType.SymmetricUdpFirewall;
res.PublicEndPoint = mappedAddress1;
return res;
}
res.NatType = NatType.OpenInternet;
res.PublicEndPoint = mappedAddress2;
return res;
}
// NAT
if (response2 != null)
{
// 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
var type = Equals(remote1.Address, remote2.Address) || remote1.Port == remote2.Port ? NatType.UnsupportedServer : NatType.FullCone;
res.NatType = type;
res.PublicEndPoint = mappedAddress2;
return res;
}
// Test I(#2)
var test12 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
var (response12, _, _) = await TestAsync(test12, changedAddress1, changedAddress1);
var mappedAddress12 = AttributeExtensions.GetMappedAddressAttribute(response12);
if (mappedAddress12 == null)
{
res.NatType = NatType.Unknown;
return res;
}
if (!Equals(mappedAddress12, mappedAddress1))
{
res.NatType = NatType.Symmetric;
res.PublicEndPoint = mappedAddress12;
return res;
}
// Test III
var test3 = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
};
var (response3, _, _) = await TestAsync(test3, changedAddress1, changedAddress1);
var mappedAddress3 = AttributeExtensions.GetMappedAddressAttribute(response3);
if (mappedAddress3 != null)
{
res.NatType = NatType.RestrictedCone;
res.PublicEndPoint = mappedAddress3;
return res;
}
res.NatType = NatType.PortRestrictedCone;
res.PublicEndPoint = mappedAddress12;
return res;
}
finally
{
await Proxy.DisconnectAsync();
_natTypeSubj.OnNext(res.NatType);
PubSubj.OnNext(res.PublicEndPoint);
}
}
protected async Task<(StunMessage5389, IPEndPoint, IPAddress)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive)
{
try
{
var b1 = sendMessage.Bytes.ToArray();
//var t = DateTime.Now;
// Simple retransmissions
//https://tools.ietf.org/html/rfc5389#section-7.2.1
//while (t + TimeSpan.FromSeconds(6) > DateTime.Now)
{
try
{
var (receive1, ipe, local) = await Proxy.RecieveAsync(b1, remote, receive);
var message = new StunMessage5389();
if (message.TryParse(receive1) &&
message.ClassicTransactionId.IsEqual(sendMessage.ClassicTransactionId))
{
return (message, ipe, local);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
return (null, null, null);
}
public virtual void Dispose()
{
Proxy?.Dispose();
_natTypeSubj.OnCompleted();
PubSubj.OnCompleted();
LocalSubj.OnCompleted();
}
}
}