StunClient3489.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using Microsoft;
  2. using STUN.Enums;
  3. using STUN.Messages;
  4. using STUN.Proxy;
  5. using STUN.StunResult;
  6. using STUN.Utils;
  7. using System;
  8. using System.Buffers;
  9. using System.Diagnostics;
  10. using System.Net;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  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. public IPEndPoint LocalEndPoint => Proxy.LocalEndPoint;
  22. public TimeSpan Timeout
  23. {
  24. get => Proxy.Timeout;
  25. set => Proxy.Timeout = value;
  26. }
  27. protected readonly IPAddress Server;
  28. protected readonly ushort Port;
  29. protected IPEndPoint RemoteEndPoint => new(Server, Port);
  30. protected readonly IUdpProxy Proxy;
  31. public ClassicStunResult Status { get; } = new();
  32. public StunClient3489(IPAddress server, ushort port = 3478, IPEndPoint? local = null, IUdpProxy? proxy = null)
  33. {
  34. Requires.NotNull(server, nameof(server));
  35. Requires.Argument(port > 0, nameof(port), @"Port value must be >= 1 !");
  36. Proxy = proxy ?? new NoneUdpProxy(local);
  37. Server = server;
  38. Port = port;
  39. Timeout = TimeSpan.FromSeconds(1.6);
  40. Status.LocalEndPoint = local;
  41. }
  42. private void Init()
  43. {
  44. Status.PublicEndPoint = default;
  45. Status.LocalEndPoint = default;
  46. Status.NatType = NatType.Unknown;
  47. }
  48. public async Task Query3489Async()
  49. {
  50. try
  51. {
  52. Init();
  53. using var cts = new CancellationTokenSource(Timeout);
  54. await Proxy.ConnectAsync(cts.Token);
  55. // test I
  56. var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
  57. var (response1, remote1, local1) = await TestAsync(test1, RemoteEndPoint, RemoteEndPoint, cts.Token);
  58. if (response1 is null || remote1 is null)
  59. {
  60. Status.NatType = NatType.UdpBlocked;
  61. return;
  62. }
  63. Status.LocalEndPoint = local1 is null ? null : new IPEndPoint(local1, LocalEndPoint.Port);
  64. var mappedAddress1 = response1.GetMappedAddressAttribute();
  65. var changedAddress1 = response1.GetChangedAddressAttribute();
  66. // 某些单 IP 服务器的迷惑操作
  67. if (mappedAddress1 is null
  68. || changedAddress1 is null
  69. || Equals(changedAddress1.Address, remote1.Address)
  70. || changedAddress1.Port == remote1.Port)
  71. {
  72. Status.NatType = NatType.UnsupportedServer;
  73. return;
  74. }
  75. Status.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址
  76. var test2 = new StunMessage5389
  77. {
  78. StunMessageType = StunMessageType.BindingRequest,
  79. MagicCookie = 0,
  80. Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
  81. };
  82. // test II
  83. var (response2, remote2, _) = await TestAsync(test2, RemoteEndPoint, changedAddress1, cts.Token);
  84. var mappedAddress2 = response2.GetMappedAddressAttribute();
  85. if (Equals(mappedAddress1.Address, local1) && mappedAddress1.Port == LocalEndPoint.Port)
  86. {
  87. // No NAT
  88. if (response2 is null)
  89. {
  90. Status.NatType = NatType.SymmetricUdpFirewall;
  91. Status.PublicEndPoint = mappedAddress1;
  92. }
  93. else
  94. {
  95. Status.NatType = NatType.OpenInternet;
  96. Status.PublicEndPoint = mappedAddress2;
  97. }
  98. return;
  99. }
  100. // NAT
  101. if (response2 is not null && remote2 is not null)
  102. {
  103. // 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
  104. var type = Equals(remote1.Address, remote2.Address) || remote1.Port == remote2.Port ? NatType.UnsupportedServer : NatType.FullCone;
  105. Status.NatType = type;
  106. Status.PublicEndPoint = mappedAddress2;
  107. return;
  108. }
  109. // Test I(#2)
  110. var test12 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
  111. var (response12, _, _) = await TestAsync(test12, changedAddress1, changedAddress1, cts.Token);
  112. var mappedAddress12 = response12.GetMappedAddressAttribute();
  113. if (mappedAddress12 is null)
  114. {
  115. Status.NatType = NatType.Unknown;
  116. return;
  117. }
  118. if (!Equals(mappedAddress12, mappedAddress1))
  119. {
  120. Status.NatType = NatType.Symmetric;
  121. Status.PublicEndPoint = mappedAddress12;
  122. return;
  123. }
  124. // Test III
  125. var test3 = new StunMessage5389
  126. {
  127. StunMessageType = StunMessageType.BindingRequest,
  128. MagicCookie = 0,
  129. Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
  130. };
  131. var (response3, _, _) = await TestAsync(test3, changedAddress1, changedAddress1, cts.Token);
  132. var mappedAddress3 = response3.GetMappedAddressAttribute();
  133. if (mappedAddress3 is not null)
  134. {
  135. Status.NatType = NatType.RestrictedCone;
  136. Status.PublicEndPoint = mappedAddress3;
  137. return;
  138. }
  139. Status.NatType = NatType.PortRestrictedCone;
  140. Status.PublicEndPoint = mappedAddress12;
  141. }
  142. finally
  143. {
  144. await Proxy.DisconnectAsync();
  145. }
  146. }
  147. protected async Task<(StunMessage5389?, IPEndPoint?, IPAddress?)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken token)
  148. {
  149. try
  150. {
  151. using var memoryOwner = MemoryPool<byte>.Shared.Rent(ushort.MaxValue);
  152. var sendBuffer = memoryOwner.Memory;
  153. var length = sendMessage.WriteTo(sendBuffer.Span);
  154. //var t = DateTime.Now;
  155. // Simple retransmissions
  156. //https://tools.ietf.org/html/rfc3489#section-9.3
  157. //while (t + TimeSpan.FromSeconds(3) > DateTime.Now)
  158. {
  159. try
  160. {
  161. var (receive1, ipe, local) = await Proxy.ReceiveAsync(sendBuffer[..length], remote, receive, token);
  162. var message = new StunMessage5389();
  163. if (message.TryParse(receive1) &&
  164. message.IsSameTransaction(sendMessage))
  165. {
  166. return (message, ipe, local);
  167. }
  168. }
  169. catch (Exception ex)
  170. {
  171. Debug.WriteLine(ex);
  172. }
  173. }
  174. }
  175. catch (Exception ex)
  176. {
  177. Debug.WriteLine(ex);
  178. }
  179. return (null, null, null);
  180. }
  181. public virtual void Dispose()
  182. {
  183. Proxy.Dispose();
  184. }
  185. }
  186. }