StunClient3489.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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.Net.Sockets;
  12. using System.Threading;
  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 : IStunClient
  21. {
  22. public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
  23. public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
  24. private readonly IPEndPoint _remoteEndPoint;
  25. private readonly IUdpProxy _proxy;
  26. public ClassicStunResult Status { get; } = new();
  27. public StunClient3489(IPAddress server, ushort port, IPEndPoint local, IUdpProxy? proxy = null)
  28. {
  29. Requires.NotNull(server, nameof(server));
  30. Requires.Argument(port > 0, nameof(port), @"Port value must be > 0!");
  31. _proxy = proxy ?? new NoneUdpProxy(local);
  32. _remoteEndPoint = new IPEndPoint(server, port);
  33. Status.LocalEndPoint = local;
  34. }
  35. public virtual async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
  36. {
  37. using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  38. cts.CancelAfter(ReceiveTimeout);
  39. await _proxy.ConnectAsync(cts.Token);
  40. }
  41. public virtual async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
  42. {
  43. await _proxy.CloseAsync(cancellationToken);
  44. }
  45. public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
  46. {
  47. Status.Reset();
  48. // test I
  49. var response1 = await Test1Async(cancellationToken);
  50. if (response1 is null)
  51. {
  52. Status.NatType = NatType.UdpBlocked;
  53. return;
  54. }
  55. Status.LocalEndPoint = new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
  56. var mappedAddress1 = response1.Message.GetMappedAddressAttribute();
  57. var changedAddress = response1.Message.GetChangedAddressAttribute();
  58. Status.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址
  59. // 某些单 IP 服务器的迷惑操作
  60. if (mappedAddress1 is null || changedAddress is null
  61. || Equals(changedAddress.Address, response1.Remote.Address)
  62. || changedAddress.Port == response1.Remote.Port)
  63. {
  64. Status.NatType = NatType.UnsupportedServer;
  65. return;
  66. }
  67. // test II
  68. var response2 = await Test2Async(changedAddress, cancellationToken);
  69. var mappedAddress2 = response2?.Message.GetMappedAddressAttribute();
  70. // is Public IP == link's IP?
  71. if (Equals(mappedAddress1.Address, response1.LocalAddress) && mappedAddress1.Port == LocalEndPoint.Port)
  72. {
  73. // No NAT
  74. if (response2 is null)
  75. {
  76. Status.NatType = NatType.SymmetricUdpFirewall;
  77. Status.PublicEndPoint = mappedAddress1;
  78. }
  79. else
  80. {
  81. Status.NatType = NatType.OpenInternet;
  82. Status.PublicEndPoint = mappedAddress2;
  83. }
  84. return;
  85. }
  86. // NAT
  87. if (response2 is not null)
  88. {
  89. // 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
  90. var type = Equals(response1.Remote.Address, response2.Remote.Address) || response1.Remote.Port == response2.Remote.Port ? NatType.UnsupportedServer : NatType.FullCone;
  91. Status.NatType = type;
  92. Status.PublicEndPoint = mappedAddress2;
  93. return;
  94. }
  95. // Test I(#2)
  96. var response12 = await Test1_2Async(changedAddress, cancellationToken);
  97. var mappedAddress12 = response12?.Message.GetMappedAddressAttribute();
  98. if (mappedAddress12 is null)
  99. {
  100. Status.NatType = NatType.Unknown;
  101. return;
  102. }
  103. if (!Equals(mappedAddress12, mappedAddress1))
  104. {
  105. Status.NatType = NatType.Symmetric;
  106. Status.PublicEndPoint = mappedAddress12;
  107. return;
  108. }
  109. // Test III
  110. var response3 = await Test3Async(cancellationToken);
  111. if (response3 is not null)
  112. {
  113. var mappedAddress3 = response3.Message.GetMappedAddressAttribute();
  114. if (mappedAddress3 is not null
  115. && Equals(response3.Remote.Address, response1.Remote.Address)
  116. && response3.Remote.Port != response1.Remote.Port)
  117. {
  118. Status.NatType = NatType.RestrictedCone;
  119. Status.PublicEndPoint = mappedAddress3;
  120. return;
  121. }
  122. }
  123. Status.NatType = NatType.PortRestrictedCone;
  124. Status.PublicEndPoint = mappedAddress12;
  125. }
  126. private async ValueTask<StunResponse?> RequestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
  127. {
  128. try
  129. {
  130. using var memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
  131. var buffer = memoryOwner.Memory;
  132. var length = sendMessage.WriteTo(buffer.Span);
  133. await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
  134. using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  135. cts.CancelAfter(ReceiveTimeout);
  136. var r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
  137. var message = new StunMessage5389();
  138. if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
  139. {
  140. return new StunResponse(message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
  141. }
  142. }
  143. catch (Exception ex)
  144. {
  145. Debug.WriteLine(ex);
  146. }
  147. return default;
  148. }
  149. public virtual async ValueTask<StunResponse?> Test1Async(CancellationToken cancellationToken)
  150. {
  151. var message = new StunMessage5389
  152. {
  153. StunMessageType = StunMessageType.BindingRequest,
  154. MagicCookie = 0
  155. };
  156. return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
  157. }
  158. public virtual async ValueTask<StunResponse?> Test2Async(IPEndPoint other, CancellationToken cancellationToken)
  159. {
  160. var message = new StunMessage5389
  161. {
  162. StunMessageType = StunMessageType.BindingRequest,
  163. MagicCookie = 0,
  164. Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
  165. };
  166. return await RequestAsync(message, _remoteEndPoint, other, cancellationToken);
  167. }
  168. public virtual async ValueTask<StunResponse?> Test1_2Async(IPEndPoint other, CancellationToken cancellationToken)
  169. {
  170. var message = new StunMessage5389
  171. {
  172. StunMessageType = StunMessageType.BindingRequest,
  173. MagicCookie = 0
  174. };
  175. return await RequestAsync(message, other, other, cancellationToken);
  176. }
  177. public virtual async ValueTask<StunResponse?> Test3Async(CancellationToken cancellationToken)
  178. {
  179. var message = new StunMessage5389
  180. {
  181. StunMessageType = StunMessageType.BindingRequest,
  182. MagicCookie = 0,
  183. Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
  184. };
  185. return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
  186. }
  187. public void Dispose()
  188. {
  189. _proxy.Dispose();
  190. }
  191. }
  192. }