StunClient3489.cs 8.7 KB

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