1
0

StunClient5389UDP.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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.Buffers;
  8. using System.Diagnostics;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Net;
  11. using System.Net.Sockets;
  12. using System.Runtime.CompilerServices;
  13. namespace STUN.Client;
  14. /// <summary>
  15. /// https://tools.ietf.org/html/rfc5389#section-7.2.1
  16. /// https://tools.ietf.org/html/rfc5780#section-4.2
  17. /// </summary>
  18. public class StunClient5389UDP : IStunClient
  19. {
  20. public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
  21. public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
  22. private readonly IPEndPoint _remoteEndPoint;
  23. private readonly IUdpProxy _proxy;
  24. public StunResult5389 State { get; } = new();
  25. public StunClient5389UDP(IPEndPoint server, IPEndPoint local, IUdpProxy? proxy = null)
  26. {
  27. Requires.NotNull(server, nameof(server));
  28. Requires.NotNull(local, nameof(local));
  29. _proxy = proxy ?? new NoneUdpProxy(local);
  30. _remoteEndPoint = server;
  31. State.LocalEndPoint = local;
  32. }
  33. public async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
  34. {
  35. using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  36. cts.CancelAfter(ReceiveTimeout);
  37. await _proxy.ConnectAsync(cts.Token);
  38. }
  39. public async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
  40. {
  41. await _proxy.CloseAsync(cancellationToken);
  42. }
  43. public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
  44. {
  45. State.Reset();
  46. await FilteringBehaviorTestBaseAsync(cancellationToken);
  47. if (State.BindingTestResult is not BindingTestResult.Success
  48. || State.FilteringBehavior is FilteringBehavior.UnsupportedServer
  49. )
  50. {
  51. return;
  52. }
  53. if (Equals(State.PublicEndPoint, State.LocalEndPoint))
  54. {
  55. State.MappingBehavior = MappingBehavior.Direct;
  56. return;
  57. }
  58. // MappingBehaviorTest test II
  59. StunResult5389 result2 = await MappingBehaviorTestBase2Async(cancellationToken);
  60. if (State.MappingBehavior is not MappingBehavior.Unknown)
  61. {
  62. return;
  63. }
  64. // MappingBehaviorTest test III
  65. await MappingBehaviorTestBase3Async(result2, cancellationToken);
  66. }
  67. public async ValueTask<StunResult5389> BindingTestAsync(CancellationToken cancellationToken = default)
  68. {
  69. return await BindingTestBaseAsync(_remoteEndPoint, cancellationToken);
  70. }
  71. public virtual async ValueTask<StunResult5389> BindingTestBaseAsync(IPEndPoint remote, CancellationToken cancellationToken = default)
  72. {
  73. StunResult5389 result = new();
  74. StunMessage5389 test = new()
  75. {
  76. StunMessageType = StunMessageType.BindingRequest
  77. };
  78. StunResponse? response1 = await RequestAsync(test, remote, remote, cancellationToken);
  79. IPEndPoint? mappedAddress1 = response1?.Message.GetXorMappedAddressAttribute();
  80. IPEndPoint? otherAddress = response1?.Message.GetOtherAddressAttribute();
  81. if (response1 is null)
  82. {
  83. result.BindingTestResult = BindingTestResult.Fail;
  84. }
  85. else if (mappedAddress1 is null)
  86. {
  87. result.BindingTestResult = BindingTestResult.UnsupportedServer;
  88. }
  89. else
  90. {
  91. result.BindingTestResult = BindingTestResult.Success;
  92. }
  93. IPEndPoint? local = response1 is null ? null : new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
  94. result.LocalEndPoint = local;
  95. result.PublicEndPoint = mappedAddress1;
  96. result.OtherEndPoint = otherAddress;
  97. return result;
  98. }
  99. public async ValueTask MappingBehaviorTestAsync(CancellationToken cancellationToken = default)
  100. {
  101. State.Reset();
  102. // test I
  103. StunResult5389 bindingResult = await BindingTestAsync(cancellationToken);
  104. State.Clone(bindingResult);
  105. if (State.BindingTestResult is not BindingTestResult.Success)
  106. {
  107. return;
  108. }
  109. if (!HasValidOtherAddress(State.OtherEndPoint))
  110. {
  111. State.MappingBehavior = MappingBehavior.UnsupportedServer;
  112. return;
  113. }
  114. if (Equals(State.PublicEndPoint, State.LocalEndPoint))
  115. {
  116. State.MappingBehavior = MappingBehavior.Direct; // or Endpoint-Independent
  117. return;
  118. }
  119. // test II
  120. StunResult5389 result2 = await MappingBehaviorTestBase2Async(cancellationToken);
  121. if (State.MappingBehavior is not MappingBehavior.Unknown)
  122. {
  123. return;
  124. }
  125. // test III
  126. await MappingBehaviorTestBase3Async(result2, cancellationToken);
  127. }
  128. private async ValueTask<StunResult5389> MappingBehaviorTestBase2Async(CancellationToken cancellationToken)
  129. {
  130. Verify.Operation(State.OtherEndPoint is not null, @"OTHER-ADDRESS is not returned");
  131. StunResult5389 result2 = await BindingTestBaseAsync(new IPEndPoint(State.OtherEndPoint.Address, _remoteEndPoint.Port), cancellationToken);
  132. if (result2.BindingTestResult is not BindingTestResult.Success)
  133. {
  134. State.MappingBehavior = MappingBehavior.Fail;
  135. }
  136. else if (Equals(result2.PublicEndPoint, State.PublicEndPoint))
  137. {
  138. State.MappingBehavior = MappingBehavior.EndpointIndependent;
  139. }
  140. return result2;
  141. }
  142. private async ValueTask MappingBehaviorTestBase3Async(StunResult5389 result2, CancellationToken cancellationToken)
  143. {
  144. Verify.Operation(State.OtherEndPoint is not null, @"OTHER-ADDRESS is not returned");
  145. StunResult5389 result3 = await BindingTestBaseAsync(State.OtherEndPoint, cancellationToken);
  146. if (result3.BindingTestResult is not BindingTestResult.Success)
  147. {
  148. State.MappingBehavior = MappingBehavior.Fail;
  149. return;
  150. }
  151. State.MappingBehavior = Equals(result3.PublicEndPoint, result2.PublicEndPoint) ? MappingBehavior.AddressDependent : MappingBehavior.AddressAndPortDependent;
  152. }
  153. public async ValueTask FilteringBehaviorTestAsync(CancellationToken cancellationToken = default)
  154. {
  155. State.Reset();
  156. await FilteringBehaviorTestBaseAsync(cancellationToken);
  157. }
  158. private async ValueTask FilteringBehaviorTestBaseAsync(CancellationToken cancellationToken)
  159. {
  160. // test I
  161. StunResult5389 bindingResult = await BindingTestAsync(cancellationToken);
  162. State.Clone(bindingResult);
  163. if (State.BindingTestResult is not BindingTestResult.Success)
  164. {
  165. return;
  166. }
  167. if (!HasValidOtherAddress(State.OtherEndPoint))
  168. {
  169. State.FilteringBehavior = FilteringBehavior.UnsupportedServer;
  170. return;
  171. }
  172. // test II
  173. StunResponse? response2 = await FilteringBehaviorTest2Async(cancellationToken);
  174. if (response2 is not null)
  175. {
  176. State.FilteringBehavior = Equals(response2.Remote, State.OtherEndPoint) ? FilteringBehavior.EndpointIndependent : FilteringBehavior.UnsupportedServer;
  177. return;
  178. }
  179. // test III
  180. StunResponse? response3 = await FilteringBehaviorTest3Async(cancellationToken);
  181. if (response3 is null)
  182. {
  183. State.FilteringBehavior = FilteringBehavior.AddressAndPortDependent;
  184. return;
  185. }
  186. if (Equals(response3.Remote.Address, _remoteEndPoint.Address) && response3.Remote.Port != _remoteEndPoint.Port)
  187. {
  188. State.FilteringBehavior = FilteringBehavior.AddressDependent;
  189. }
  190. else
  191. {
  192. State.FilteringBehavior = FilteringBehavior.UnsupportedServer;
  193. }
  194. }
  195. public virtual async ValueTask<StunResponse?> FilteringBehaviorTest2Async(CancellationToken cancellationToken = default)
  196. {
  197. Assumes.NotNull(State.OtherEndPoint);
  198. StunMessage5389 message = new()
  199. {
  200. StunMessageType = StunMessageType.BindingRequest,
  201. Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
  202. };
  203. return await RequestAsync(message, _remoteEndPoint, State.OtherEndPoint, cancellationToken);
  204. }
  205. public virtual async ValueTask<StunResponse?> FilteringBehaviorTest3Async(CancellationToken cancellationToken = default)
  206. {
  207. Assumes.NotNull(State.OtherEndPoint);
  208. StunMessage5389 message = new()
  209. {
  210. StunMessageType = StunMessageType.BindingRequest,
  211. Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
  212. };
  213. return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
  214. }
  215. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  216. private bool HasValidOtherAddress([NotNullWhen(true)] IPEndPoint? other)
  217. {
  218. return other is not null
  219. && !Equals(other.Address, _remoteEndPoint.Address)
  220. && other.Port != _remoteEndPoint.Port;
  221. }
  222. private async ValueTask<StunResponse?> RequestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
  223. {
  224. try
  225. {
  226. using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
  227. Memory<byte> buffer = memoryOwner.Memory;
  228. int length = sendMessage.WriteTo(buffer.Span);
  229. await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
  230. using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  231. cts.CancelAfter(ReceiveTimeout);
  232. SocketReceiveMessageFromResult r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
  233. StunMessage5389 message = new();
  234. if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
  235. {
  236. return new StunResponse(message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
  237. }
  238. }
  239. catch (Exception ex)
  240. {
  241. Debug.WriteLine(ex);
  242. }
  243. return default;
  244. }
  245. public void Dispose()
  246. {
  247. _proxy.Dispose();
  248. }
  249. }