|
@@ -8,7 +8,8 @@ using System.Diagnostics;
|
|
|
using System.Linq;
|
|
|
using System.Net;
|
|
|
using System.Net.Sockets;
|
|
|
-using System.Threading.Tasks;
|
|
|
+using System.Reactive.Linq;
|
|
|
+using System.Reactive.Subjects;
|
|
|
|
|
|
namespace STUN.Client
|
|
|
{
|
|
@@ -16,8 +17,21 @@ namespace STUN.Client
|
|
|
/// https://tools.ietf.org/html/rfc3489#section-10.1
|
|
|
/// https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
|
|
|
/// </summary>
|
|
|
- public class StunClient3489 : IStunClient
|
|
|
+ public class StunClient3489 : IDisposable
|
|
|
{
|
|
|
+ #region Subject
|
|
|
+
|
|
|
+ private readonly Subject<NatType> _natTypeSubj = new Subject<NatType>();
|
|
|
+ public IObservable<NatType> NatTypeChanged => _natTypeSubj.AsObservable();
|
|
|
+
|
|
|
+ private readonly Subject<IPEndPoint> _pubSubj = new Subject<IPEndPoint>();
|
|
|
+ public IObservable<IPEndPoint> PubChanged => _pubSubj.AsObservable();
|
|
|
+
|
|
|
+ private readonly Subject<IPEndPoint> _localSubj = new Subject<IPEndPoint>();
|
|
|
+ public IObservable<IPEndPoint> LocalChanged => _localSubj.AsObservable();
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
public IPEndPoint LocalEndPoint => (IPEndPoint)UdpClient.Client.LocalEndPoint;
|
|
|
|
|
|
public TimeSpan Timeout
|
|
@@ -67,88 +81,121 @@ namespace STUN.Client
|
|
|
Timeout = TimeSpan.FromSeconds(1.6);
|
|
|
}
|
|
|
|
|
|
- public virtual IStunResult Query()
|
|
|
+ public ClassicStunResult Query()
|
|
|
{
|
|
|
- // test I
|
|
|
- var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
|
|
|
+ var res = new ClassicStunResult();
|
|
|
+ _natTypeSubj.OnNext(res.NatType);
|
|
|
+ _pubSubj.OnNext(res.PublicEndPoint);
|
|
|
|
|
|
- var (response1, remote1, local1) = Test(test1, RemoteEndPoint, RemoteEndPoint);
|
|
|
- if (response1 == null)
|
|
|
- {
|
|
|
- return new ClassicStunResult(NatType.UdpBlocked, null);
|
|
|
- }
|
|
|
- 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)
|
|
|
+ try
|
|
|
{
|
|
|
- return new ClassicStunResult(NatType.UnsupportedServer, null);
|
|
|
- }
|
|
|
+ // test I
|
|
|
+ var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
|
|
|
|
|
|
- var test2 = new StunMessage5389
|
|
|
- {
|
|
|
- StunMessageType = StunMessageType.BindingRequest,
|
|
|
- MagicCookie = 0,
|
|
|
- Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
|
|
|
- };
|
|
|
+ var (response1, remote1, local1) = Test(test1, RemoteEndPoint, RemoteEndPoint);
|
|
|
+ if (response1 == null)
|
|
|
+ {
|
|
|
+ res.NatType = NatType.UdpBlocked;
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (local1 != null)
|
|
|
+ {
|
|
|
+ _localSubj.OnNext(LocalEndPoint);
|
|
|
+ }
|
|
|
|
|
|
- // test II
|
|
|
- var (response2, remote2, _) = Test(test2, RemoteEndPoint, changedAddress1);
|
|
|
- var mappedAddress2 = AttributeExtensions.GetMappedAddressAttribute(response2);
|
|
|
+ var mappedAddress1 = AttributeExtensions.GetMappedAddressAttribute(response1);
|
|
|
+ var changedAddress1 = AttributeExtensions.GetChangedAddressAttribute(response1);
|
|
|
|
|
|
- if (Equals(mappedAddress1.Address, local1) && mappedAddress1.Port == LocalEndPoint.Port)
|
|
|
- {
|
|
|
- // No NAT
|
|
|
- if (response2 == null)
|
|
|
+ // 某些单 IP 服务器的迷惑操作
|
|
|
+ if (mappedAddress1 == null
|
|
|
+ || changedAddress1 == null
|
|
|
+ || Equals(changedAddress1.Address, remote1.Address)
|
|
|
+ || changedAddress1.Port == remote1.Port)
|
|
|
{
|
|
|
- return new ClassicStunResult(NatType.SymmetricUdpFirewall, mappedAddress1);
|
|
|
+ res.NatType = NatType.UnsupportedServer;
|
|
|
+ return res;
|
|
|
}
|
|
|
- return new ClassicStunResult(NatType.OpenInternet, mappedAddress2);
|
|
|
- }
|
|
|
|
|
|
- // NAT
|
|
|
- if (response2 != null)
|
|
|
- {
|
|
|
- // 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
|
|
|
- var type = Equals(remote1.Address, remote2.Address) || remote1.Port == remote2.Port ? NatType.UnsupportedServer : NatType.FullCone;
|
|
|
- return new ClassicStunResult(type, mappedAddress2);
|
|
|
- }
|
|
|
+ _pubSubj.OnNext(mappedAddress1); // 显示 test I 得到的映射地址
|
|
|
|
|
|
- // Test I(#2)
|
|
|
- var test12 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
|
|
|
- var (response12, _, _) = Test(test12, changedAddress1, changedAddress1);
|
|
|
- var mappedAddress12 = AttributeExtensions.GetMappedAddressAttribute(response12);
|
|
|
+ var test2 = new StunMessage5389
|
|
|
+ {
|
|
|
+ StunMessageType = StunMessageType.BindingRequest,
|
|
|
+ MagicCookie = 0,
|
|
|
+ Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
|
|
|
+ };
|
|
|
|
|
|
- if (mappedAddress12 == null) return new ClassicStunResult(NatType.Unknown, null);
|
|
|
+ // test II
|
|
|
+ var (response2, remote2, _) = Test(test2, RemoteEndPoint, changedAddress1);
|
|
|
+ var mappedAddress2 = AttributeExtensions.GetMappedAddressAttribute(response2);
|
|
|
|
|
|
- if (!Equals(mappedAddress12, mappedAddress1))
|
|
|
- {
|
|
|
- return new ClassicStunResult(NatType.Symmetric, mappedAddress12);
|
|
|
- }
|
|
|
+ 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;
|
|
|
+ }
|
|
|
|
|
|
- // Test III
|
|
|
- var test3 = new StunMessage5389
|
|
|
- {
|
|
|
- StunMessageType = StunMessageType.BindingRequest,
|
|
|
- MagicCookie = 0,
|
|
|
- Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
|
|
|
- };
|
|
|
- var (response3, _, _) = Test(test3, changedAddress1, changedAddress1);
|
|
|
- var mappedAddress3 = AttributeExtensions.GetMappedAddressAttribute(response3);
|
|
|
- if (mappedAddress3 != null)
|
|
|
+ // 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, _, _) = Test(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, _, _) = Test(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
|
|
|
{
|
|
|
- return new ClassicStunResult(NatType.RestrictedCone, mappedAddress3);
|
|
|
+ _natTypeSubj.OnNext(res.NatType);
|
|
|
+ _pubSubj.OnNext(res.PublicEndPoint);
|
|
|
}
|
|
|
- return new ClassicStunResult(NatType.PortRestrictedCone, mappedAddress12);
|
|
|
- }
|
|
|
-
|
|
|
- public virtual Task<IStunResult> QueryAsync()
|
|
|
- {
|
|
|
- throw new NotImplementedException();
|
|
|
}
|
|
|
|
|
|
/// <returns>
|
|
@@ -192,6 +239,8 @@ namespace STUN.Client
|
|
|
public void Dispose()
|
|
|
{
|
|
|
UdpClient?.Dispose();
|
|
|
+ _natTypeSubj.OnCompleted();
|
|
|
+ _pubSubj.OnCompleted();
|
|
|
}
|
|
|
}
|
|
|
}
|