StunClient3489.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. using STUN.Enums;
  2. using STUN.Interfaces;
  3. using STUN.Message;
  4. using STUN.Proxy;
  5. using STUN.StunResult;
  6. using STUN.Utils;
  7. using System;
  8. using System.Diagnostics;
  9. using System.Linq;
  10. using System.Net;
  11. using System.Reactive.Linq;
  12. using System.Reactive.Subjects;
  13. using System.Threading.Tasks;
  14. namespace STUN.Client
  15. {
  16. /// <summary>
  17. /// https://tools.ietf.org/html/rfc3489#section-10.1
  18. /// https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
  19. /// </summary>
  20. public class StunClient3489 : IDisposable
  21. {
  22. #region Subject
  23. private readonly Subject<NatType> _natTypeSubj = new Subject<NatType>();
  24. public IObservable<NatType> NatTypeChanged => _natTypeSubj.AsObservable();
  25. protected readonly Subject<IPEndPoint> PubSubj = new Subject<IPEndPoint>();
  26. public IObservable<IPEndPoint> PubChanged => PubSubj.AsObservable();
  27. protected readonly Subject<IPEndPoint> LocalSubj = new Subject<IPEndPoint>();
  28. public IObservable<IPEndPoint> LocalChanged => LocalSubj.AsObservable();
  29. #endregion
  30. public IPEndPoint LocalEndPoint => Proxy.LocalEndPoint;
  31. public TimeSpan Timeout
  32. {
  33. get => Proxy.Timeout;
  34. set => Proxy.Timeout = value;
  35. }
  36. protected readonly IPAddress Server;
  37. protected readonly ushort Port;
  38. public IPEndPoint RemoteEndPoint => Server == null ? null : new IPEndPoint(Server, Port);
  39. protected IUdpProxy Proxy;
  40. public StunClient3489(string server, ushort port = 3478, IPEndPoint local = null, IUdpProxy proxy = null, IDnsQuery dnsQuery = null)
  41. {
  42. Proxy = proxy ?? new NoneUdpProxy(local);
  43. if (string.IsNullOrEmpty(server))
  44. {
  45. throw new ArgumentException(@"Please specify STUN server !");
  46. }
  47. if (port < 1)
  48. {
  49. throw new ArgumentException(@"Port value must be >= 1 !");
  50. }
  51. dnsQuery ??= new DefaultDnsQuery();
  52. Server = dnsQuery.Query(server);
  53. if (Server == null)
  54. {
  55. throw new ArgumentException(@"Wrong STUN server !");
  56. }
  57. Port = port;
  58. Timeout = TimeSpan.FromSeconds(1.6);
  59. }
  60. public async Task<ClassicStunResult> Query3489Async()
  61. {
  62. var res = new ClassicStunResult();
  63. _natTypeSubj.OnNext(res.NatType);
  64. PubSubj.OnNext(res.PublicEndPoint);
  65. try
  66. {
  67. await Proxy.ConnectAsync();
  68. // test I
  69. var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
  70. var (response1, remote1, local1) = await TestAsync(test1, RemoteEndPoint, RemoteEndPoint);
  71. if (response1 == null)
  72. {
  73. res.NatType = NatType.UdpBlocked;
  74. return res;
  75. }
  76. if (local1 != null)
  77. {
  78. LocalSubj.OnNext(LocalEndPoint);
  79. }
  80. var mappedAddress1 = AttributeExtensions.GetMappedAddressAttribute(response1);
  81. var changedAddress1 = AttributeExtensions.GetChangedAddressAttribute(response1);
  82. // 某些单 IP 服务器的迷惑操作
  83. if (mappedAddress1 == null
  84. || changedAddress1 == null
  85. || Equals(changedAddress1.Address, remote1.Address)
  86. || changedAddress1.Port == remote1.Port)
  87. {
  88. res.NatType = NatType.UnsupportedServer;
  89. return res;
  90. }
  91. PubSubj.OnNext(mappedAddress1); // 显示 test I 得到的映射地址
  92. var test2 = new StunMessage5389
  93. {
  94. StunMessageType = StunMessageType.BindingRequest,
  95. MagicCookie = 0,
  96. Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
  97. };
  98. // test II
  99. var (response2, remote2, _) = await TestAsync(test2, RemoteEndPoint, changedAddress1);
  100. var mappedAddress2 = AttributeExtensions.GetMappedAddressAttribute(response2);
  101. if (Equals(mappedAddress1.Address, local1) && mappedAddress1.Port == LocalEndPoint.Port)
  102. {
  103. // No NAT
  104. if (response2 == null)
  105. {
  106. res.NatType = NatType.SymmetricUdpFirewall;
  107. res.PublicEndPoint = mappedAddress1;
  108. return res;
  109. }
  110. res.NatType = NatType.OpenInternet;
  111. res.PublicEndPoint = mappedAddress2;
  112. return res;
  113. }
  114. // NAT
  115. if (response2 != null)
  116. {
  117. // 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
  118. var type = Equals(remote1.Address, remote2.Address) || remote1.Port == remote2.Port ? NatType.UnsupportedServer : NatType.FullCone;
  119. res.NatType = type;
  120. res.PublicEndPoint = mappedAddress2;
  121. return res;
  122. }
  123. // Test I(#2)
  124. var test12 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
  125. var (response12, _, _) = await TestAsync(test12, changedAddress1, changedAddress1);
  126. var mappedAddress12 = AttributeExtensions.GetMappedAddressAttribute(response12);
  127. if (mappedAddress12 == null)
  128. {
  129. res.NatType = NatType.Unknown;
  130. return res;
  131. }
  132. if (!Equals(mappedAddress12, mappedAddress1))
  133. {
  134. res.NatType = NatType.Symmetric;
  135. res.PublicEndPoint = mappedAddress12;
  136. return res;
  137. }
  138. // Test III
  139. var test3 = new StunMessage5389
  140. {
  141. StunMessageType = StunMessageType.BindingRequest,
  142. MagicCookie = 0,
  143. Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
  144. };
  145. var (response3, _, _) = await TestAsync(test3, changedAddress1, changedAddress1);
  146. var mappedAddress3 = AttributeExtensions.GetMappedAddressAttribute(response3);
  147. if (mappedAddress3 != null)
  148. {
  149. res.NatType = NatType.RestrictedCone;
  150. res.PublicEndPoint = mappedAddress3;
  151. return res;
  152. }
  153. res.NatType = NatType.PortRestrictedCone;
  154. res.PublicEndPoint = mappedAddress12;
  155. return res;
  156. }
  157. finally
  158. {
  159. await Proxy.DisconnectAsync();
  160. _natTypeSubj.OnNext(res.NatType);
  161. PubSubj.OnNext(res.PublicEndPoint);
  162. }
  163. }
  164. protected async Task<(StunMessage5389, IPEndPoint, IPAddress)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive)
  165. {
  166. try
  167. {
  168. var b1 = sendMessage.Bytes.ToArray();
  169. //var t = DateTime.Now;
  170. // Simple retransmissions
  171. //https://tools.ietf.org/html/rfc3489#section-9.3
  172. //while (t + TimeSpan.FromSeconds(3) > DateTime.Now)
  173. {
  174. try
  175. {
  176. var (receive1, ipe, local) = await Proxy.ReceiveAsync(b1, remote, receive);
  177. var message = new StunMessage5389();
  178. if (message.TryParse(receive1) &&
  179. message.ClassicTransactionId.IsEqual(sendMessage.ClassicTransactionId))
  180. {
  181. return (message, ipe, local);
  182. }
  183. }
  184. catch (Exception ex)
  185. {
  186. Debug.WriteLine(ex);
  187. }
  188. }
  189. }
  190. catch (Exception ex)
  191. {
  192. Debug.WriteLine(ex);
  193. }
  194. return (null, null, null);
  195. }
  196. public virtual void Dispose()
  197. {
  198. Proxy?.Dispose();
  199. _natTypeSubj.OnCompleted();
  200. PubSubj.OnCompleted();
  201. LocalSubj.OnCompleted();
  202. }
  203. }
  204. }