StunClient5389UDP.cs 9.0 KB

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