StunClient3489.cs 8.7 KB

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