浏览代码

Refactor: Remake UDP proxy

Bruce Wayne 4 年之前
父节点
当前提交
f89021b05b

+ 4 - 1
NatTypeTester-Console/Program.cs

@@ -17,7 +17,10 @@ if (args.Length > 0 && StunServer.TryParse(args[0], out var stun))
 
 if (args.Length > 1)
 {
-	IPEndPoint.TryParse(args[2], out local);
+	if (IPEndPoint.TryParse(args[2], out var ipEndPoint))
+	{
+		local = ipEndPoint;
+	}
 }
 
 var dnsClient = new DefaultDnsClient();

+ 2 - 1
NatTypeTester.ViewModels/MainWindowViewModel.cs

@@ -1,5 +1,6 @@
 using DynamicData;
 using DynamicData.Binding;
+using Microsoft.VisualStudio.Threading;
 using NatTypeTester.Models;
 using ReactiveUI;
 using STUN;
@@ -69,7 +70,7 @@ namespace NatTypeTester.ViewModels
 						List.Add(stun.ToString());
 					}
 				}
-			});
+			}).Forget();
 		}
 	}
 }

+ 36 - 11
NatTypeTester.ViewModels/RFC3489ViewModel.cs

@@ -3,12 +3,14 @@ using JetBrains.Annotations;
 using Microsoft;
 using NatTypeTester.Models;
 using ReactiveUI;
+using Socks5.Models;
 using STUN;
 using STUN.Client;
 using STUN.Proxy;
 using STUN.StunResult;
 using System;
 using System.Net;
+using System.Net.Sockets;
 using System.Reactive;
 using System.Reactive.Linq;
 using System.Threading;
@@ -30,22 +32,35 @@ namespace NatTypeTester.ViewModels
 
 		public ReactiveCommand<Unit, Unit> TestClassicNatType { get; }
 
+		private static readonly IPEndPoint DefaultLocalEndpoint = new(IPAddress.Any, 0);
+
 		public RFC3489ViewModel()
 		{
-			Result3489 = new ClassicStunResult { LocalEndPoint = new IPEndPoint(IPAddress.Any, 0) };
-			TestClassicNatType = ReactiveCommand.CreateFromTask(TestClassicNatTypeImpl);
+			Result3489 = new ClassicStunResult
+			{
+				LocalEndPoint = DefaultLocalEndpoint
+			};
+			TestClassicNatType = ReactiveCommand.CreateFromTask(TestClassicNatTypeAsync);
 		}
 
-		private async Task TestClassicNatTypeImpl(CancellationToken token)
+		private async Task TestClassicNatTypeAsync(CancellationToken token)
 		{
 			Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
 
-			using var proxy = ProxyFactory.CreateProxy(
-					Config.ProxyType,
-					Result3489.LocalEndPoint,
-					IPEndPoint.Parse(Config.ProxyServer),
-					Config.ProxyUser, Config.ProxyPassword
-			);
+			var proxyIpe = IPEndPoint.Parse(Config.ProxyServer);
+			var socks5Option = new Socks5CreateOption
+			{
+				Address = proxyIpe.Address,
+				Port = (ushort)proxyIpe.Port,
+				UsernamePassword = new UsernamePassword
+				{
+					UserName = Config.ProxyUser,
+					Password = Config.ProxyPassword
+				}
+			};
+
+			Result3489.LocalEndPoint ??= DefaultLocalEndpoint;
+			using var proxy = ProxyFactory.CreateProxy(Config.ProxyType, Result3489.LocalEndPoint, socks5Option);
 
 			var ip = await DnsClient.QueryAsync(server.Hostname, token);
 			using var client = new StunClient3489(ip, server.Port, Result3489.LocalEndPoint, proxy);
@@ -55,10 +70,20 @@ namespace NatTypeTester.ViewModels
 					.ObserveOn(RxApp.MainThreadScheduler)
 					.Subscribe(_ => this.RaisePropertyChanged(nameof(Result3489))))
 			{
-				await client.QueryAsync(token);
+				await client.ConnectProxyAsync(token);
+				try
+				{
+					await client.QueryAsync(token);
+					Result3489.LocalEndPoint = new IPEndPoint(client.LocalEndPoint.AddressFamily is AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, client.LocalEndPoint.Port);
+				}
+				finally
+				{
+					await client.CloseProxyAsync(token);
+				}
 			}
 
-			Result3489.LocalEndPoint = client.LocalEndPoint;
+			Result3489 = new ClassicStunResult();
+			Result3489.Clone(client.Status);
 
 			this.RaisePropertyChanged(nameof(Result3489));
 		}

+ 32 - 13
NatTypeTester.ViewModels/RFC5780ViewModel.cs

@@ -3,12 +3,14 @@ using JetBrains.Annotations;
 using Microsoft;
 using NatTypeTester.Models;
 using ReactiveUI;
+using Socks5.Models;
 using STUN;
 using STUN.Client;
 using STUN.Proxy;
 using STUN.StunResult;
 using System;
 using System.Net;
+using System.Net.Sockets;
 using System.Reactive;
 using System.Reactive.Linq;
 using System.Threading;
@@ -30,22 +32,32 @@ namespace NatTypeTester.ViewModels
 
 		public ReactiveCommand<Unit, Unit> DiscoveryNatType { get; }
 
+		private static readonly IPEndPoint DefaultLocalEndpoint = new(IPAddress.Any, 0);
+
 		public RFC5780ViewModel()
 		{
 			Result5389 = new StunResult5389 { LocalEndPoint = new IPEndPoint(IPAddress.Any, 0) };
-			DiscoveryNatType = ReactiveCommand.CreateFromTask(DiscoveryNatTypeImpl);
+			DiscoveryNatType = ReactiveCommand.CreateFromTask(DiscoveryNatTypeAsync);
 		}
 
-		private async Task DiscoveryNatTypeImpl(CancellationToken token)
+		private async Task DiscoveryNatTypeAsync(CancellationToken token)
 		{
 			Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
 
-			using var proxy = ProxyFactory.CreateProxy(
-					Config.ProxyType,
-					Result5389.LocalEndPoint,
-					IPEndPoint.Parse(Config.ProxyServer),
-					Config.ProxyUser, Config.ProxyPassword
-			);
+			var proxyIpe = IPEndPoint.Parse(Config.ProxyServer);
+			var socks5Option = new Socks5CreateOption
+			{
+				Address = proxyIpe.Address,
+				Port = (ushort)proxyIpe.Port,
+				UsernamePassword = new UsernamePassword
+				{
+					UserName = Config.ProxyUser,
+					Password = Config.ProxyPassword
+				}
+			};
+
+			Result5389.LocalEndPoint ??= DefaultLocalEndpoint;
+			using var proxy = ProxyFactory.CreateProxy(Config.ProxyType, Result5389.LocalEndPoint, socks5Option);
 
 			var ip = await DnsClient.QueryAsync(server.Hostname, token);
 			using var client = new StunClient5389UDP(ip, server.Port, Result5389.LocalEndPoint, proxy);
@@ -55,13 +67,20 @@ namespace NatTypeTester.ViewModels
 					.ObserveOn(RxApp.MainThreadScheduler)
 					.Subscribe(_ => this.RaisePropertyChanged(nameof(Result5389))))
 			{
-				await client.QueryAsync();
+				await client.ConnectProxyAsync(token);
+				try
+				{
+					await client.QueryAsync(token);
+					Result5389.LocalEndPoint = new IPEndPoint(client.LocalEndPoint.AddressFamily is AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, client.LocalEndPoint.Port);
+				}
+				finally
+				{
+					await client.CloseProxyAsync(token);
+				}
 			}
 
-			var cache = new StunResult5389();
-			cache.Clone(client.Status);
-			cache.LocalEndPoint = client.LocalEndPoint;
-			Result5389 = cache;
+			Result5389 = new StunResult5389();
+			Result5389.Clone(client.Status);
 
 			this.RaisePropertyChanged(nameof(Result5389));
 		}

+ 1 - 0
NatTypeTester/App.xaml.cs

@@ -5,6 +5,7 @@ using System;
 using System.Windows;
 using Volo.Abp;
 
+#pragma warning disable VSTHRD100 // 避免使用 Async Void 方法
 namespace NatTypeTester
 {
 	public partial class App

+ 13 - 0
STUN/Client/IStunClient.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace STUN.Client
+{
+	public interface IStunClient : IDisposable
+	{
+		ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default);
+		ValueTask CloseProxyAsync(CancellationToken cancellationToken = default);
+		ValueTask QueryAsync(CancellationToken cancellationToken = default);
+	}
+}

+ 90 - 101
STUN/Client/StunClient3489.cs

@@ -8,6 +8,7 @@ using System;
 using System.Buffers;
 using System.Diagnostics;
 using System.Net;
+using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -17,15 +18,11 @@ namespace STUN.Client
 	/// https://tools.ietf.org/html/rfc3489#section-10.1
 	/// https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
 	/// </summary>
-	public class StunClient3489 : IDisposable
+	public class StunClient3489 : IStunClient
 	{
-		public virtual IPEndPoint LocalEndPoint => _proxy.LocalEndPoint;
+		public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
 
-		public TimeSpan Timeout
-		{
-			get => _proxy.Timeout;
-			set => _proxy.Timeout = value;
-		}
+		public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
 
 		private readonly IPEndPoint _remoteEndPoint;
 
@@ -33,7 +30,7 @@ namespace STUN.Client
 
 		public ClassicStunResult Status { get; } = new();
 
-		public StunClient3489(IPAddress server, ushort port = 3478, IPEndPoint? local = null, IUdpProxy? proxy = null)
+		public StunClient3489(IPAddress server, ushort port, IPEndPoint local, IUdpProxy? proxy = null)
 		{
 			Requires.NotNull(server, nameof(server));
 			Requires.Argument(port > 0, nameof(port), @"Port value must be > 0!");
@@ -42,143 +39,135 @@ namespace STUN.Client
 
 			_remoteEndPoint = new IPEndPoint(server, port);
 
-			Timeout = TimeSpan.FromSeconds(3);
 			Status.LocalEndPoint = local;
 		}
 
-		public virtual async ValueTask ConnectAsync(CancellationToken cancellationToken)
+		public virtual async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
 		{
-			Status.Reset();
-
 			using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-			cts.CancelAfter(Timeout);
+			cts.CancelAfter(ReceiveTimeout);
 
 			await _proxy.ConnectAsync(cts.Token);
 		}
 
-		public virtual async ValueTask DisconnectAsync()
+		public virtual async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
 		{
-			await _proxy.DisconnectAsync();
+			await _proxy.CloseAsync(cancellationToken);
 		}
 
-		public async Task QueryAsync(CancellationToken cancellationToken = default)
+		public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
 		{
-			try
-			{
-				await ConnectAsync(cancellationToken);
+			Status.Reset();
 
-				// test I
-				var response1 = await Test1Async(cancellationToken);
-				if (response1 is null)
-				{
-					Status.NatType = NatType.UdpBlocked;
-					return;
-				}
+			// test I
+			var response1 = await Test1Async(cancellationToken);
+			if (response1 is null)
+			{
+				Status.NatType = NatType.UdpBlocked;
+				return;
+			}
 
-				Status.LocalEndPoint = new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
+			Status.LocalEndPoint = new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
 
-				var mappedAddress1 = response1.Message.GetMappedAddressAttribute();
-				var changedAddress = response1.Message.GetChangedAddressAttribute();
+			var mappedAddress1 = response1.Message.GetMappedAddressAttribute();
+			var changedAddress = response1.Message.GetChangedAddressAttribute();
 
-				Status.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址
+			Status.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址
 
-				// 某些单 IP 服务器的迷惑操作
-				if (mappedAddress1 is null || changedAddress is null
-					|| Equals(changedAddress.Address, response1.Remote.Address)
-					|| changedAddress.Port == response1.Remote.Port)
-				{
-					Status.NatType = NatType.UnsupportedServer;
-					return;
-				}
+			// 某些单 IP 服务器的迷惑操作
+			if (mappedAddress1 is null || changedAddress is null
+				|| Equals(changedAddress.Address, response1.Remote.Address)
+				|| changedAddress.Port == response1.Remote.Port)
+			{
+				Status.NatType = NatType.UnsupportedServer;
+				return;
+			}
 
-				// test II
-				var response2 = await Test2Async(changedAddress, cancellationToken);
-				var mappedAddress2 = response2?.Message.GetMappedAddressAttribute();
+			// test II
+			var response2 = await Test2Async(changedAddress, cancellationToken);
+			var mappedAddress2 = response2?.Message.GetMappedAddressAttribute();
 
-				// is Public IP == link's IP?
-				if (Equals(mappedAddress1.Address, response1.LocalAddress) && mappedAddress1.Port == LocalEndPoint.Port)
+			// is Public IP == link's IP?
+			if (Equals(mappedAddress1.Address, response1.LocalAddress) && mappedAddress1.Port == LocalEndPoint.Port)
+			{
+				// No NAT
+				if (response2 is null)
 				{
-					// No NAT
-					if (response2 is null)
-					{
-						Status.NatType = NatType.SymmetricUdpFirewall;
-						Status.PublicEndPoint = mappedAddress1;
-					}
-					else
-					{
-						Status.NatType = NatType.OpenInternet;
-						Status.PublicEndPoint = mappedAddress2;
-					}
-					return;
+					Status.NatType = NatType.SymmetricUdpFirewall;
+					Status.PublicEndPoint = mappedAddress1;
 				}
-
-				// NAT
-				if (response2 is not null)
+				else
 				{
-					// 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
-					var type = Equals(response1.Remote.Address, response2.Remote.Address) || response1.Remote.Port == response2.Remote.Port ? NatType.UnsupportedServer : NatType.FullCone;
-					Status.NatType = type;
+					Status.NatType = NatType.OpenInternet;
 					Status.PublicEndPoint = mappedAddress2;
-					return;
 				}
+				return;
+			}
 
-				// Test I(#2)
-				var response12 = await Test1_2Async(changedAddress, cancellationToken);
-				var mappedAddress12 = response12?.Message.GetMappedAddressAttribute();
-
-				if (mappedAddress12 is null)
-				{
-					Status.NatType = NatType.Unknown;
-					return;
-				}
+			// NAT
+			if (response2 is not null)
+			{
+				// 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
+				var type = Equals(response1.Remote.Address, response2.Remote.Address) || response1.Remote.Port == response2.Remote.Port ? NatType.UnsupportedServer : NatType.FullCone;
+				Status.NatType = type;
+				Status.PublicEndPoint = mappedAddress2;
+				return;
+			}
 
-				if (!Equals(mappedAddress12, mappedAddress1))
-				{
-					Status.NatType = NatType.Symmetric;
-					Status.PublicEndPoint = mappedAddress12;
-					return;
-				}
+			// Test I(#2)
+			var response12 = await Test1_2Async(changedAddress, cancellationToken);
+			var mappedAddress12 = response12?.Message.GetMappedAddressAttribute();
 
-				// Test III
-				var response3 = await Test3Async(cancellationToken);
-				if (response3 is not null)
-				{
-					var mappedAddress3 = response3.Message.GetMappedAddressAttribute();
-					if (mappedAddress3 is not null
-						&& Equals(response3.Remote.Address, response1.Remote.Address)
-						&& response3.Remote.Port != response1.Remote.Port)
-					{
-						Status.NatType = NatType.RestrictedCone;
-						Status.PublicEndPoint = mappedAddress3;
-						return;
-					}
-				}
+			if (mappedAddress12 is null)
+			{
+				Status.NatType = NatType.Unknown;
+				return;
+			}
 
-				Status.NatType = NatType.PortRestrictedCone;
+			if (!Equals(mappedAddress12, mappedAddress1))
+			{
+				Status.NatType = NatType.Symmetric;
 				Status.PublicEndPoint = mappedAddress12;
+				return;
 			}
-			finally
+
+			// Test III
+			var response3 = await Test3Async(cancellationToken);
+			if (response3 is not null)
 			{
-				await DisconnectAsync();
+				var mappedAddress3 = response3.Message.GetMappedAddressAttribute();
+				if (mappedAddress3 is not null
+					&& Equals(response3.Remote.Address, response1.Remote.Address)
+					&& response3.Remote.Port != response1.Remote.Port)
+				{
+					Status.NatType = NatType.RestrictedCone;
+					Status.PublicEndPoint = mappedAddress3;
+					return;
+				}
 			}
+
+			Status.NatType = NatType.PortRestrictedCone;
+			Status.PublicEndPoint = mappedAddress12;
 		}
 
 		private async ValueTask<StunResponse?> RequestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
 		{
 			try
 			{
-				using var memoryOwner = MemoryPool<byte>.Shared.Rent(ushort.MaxValue);
-				var sendBuffer = memoryOwner.Memory;
-				var length = sendMessage.WriteTo(sendBuffer.Span);
+				using var memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
+				var buffer = memoryOwner.Memory;
+				var length = sendMessage.WriteTo(buffer.Span);
+
+				await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
 
 				using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-				cts.CancelAfter(Timeout);
-				var (receiveBuffer, ipe, local) = await _proxy.ReceiveAsync(sendBuffer[..length], remote, receive, cts.Token);
+				cts.CancelAfter(ReceiveTimeout);
+				var r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
 
 				var message = new StunMessage5389();
-				if (message.TryParse(receiveBuffer) && message.IsSameTransaction(sendMessage))
+				if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
 				{
-					return new StunResponse(message, ipe, local);
+					return new StunResponse(message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
 				}
 			}
 			catch (Exception ex)

+ 75 - 89
STUN/Client/StunClient5389UDP.cs

@@ -8,6 +8,7 @@ using System;
 using System.Buffers;
 using System.Diagnostics;
 using System.Net;
+using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -17,75 +18,70 @@ namespace STUN.Client
 	/// https://tools.ietf.org/html/rfc5389#section-7.2.1
 	/// https://tools.ietf.org/html/rfc5780#section-4.2
 	/// </summary>
-	public class StunClient5389UDP : IDisposable
+	public class StunClient5389UDP : IStunClient
 	{
-		public IPEndPoint LocalEndPoint => Proxy.LocalEndPoint;
+		public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
 
-		public TimeSpan Timeout
-		{
-			get => Proxy.Timeout;
-			set => Proxy.Timeout = value;
-		}
-
-		protected readonly IPAddress Server;
-		protected readonly ushort Port;
+		public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
 
-		protected IPEndPoint RemoteEndPoint => new(Server, Port);
+		private readonly IPEndPoint _remoteEndPoint;
 
-		protected readonly IUdpProxy Proxy;
+		private readonly IUdpProxy _proxy;
 
 		public StunResult5389 Status { get; } = new();
 
-		public StunClient5389UDP(IPAddress server, ushort port = 3478, IPEndPoint? local = null, IUdpProxy? proxy = null)
+		public StunClient5389UDP(IPAddress server, ushort port, IPEndPoint local, IUdpProxy? proxy = null)
 		{
 			Requires.NotNull(server, nameof(server));
-			Requires.Argument(port > 0, nameof(port), @"Port value must be >= 1 !");
+			Requires.Argument(port > 0, nameof(port), @"Port value must be > 0!");
 
-			Proxy = proxy ?? new NoneUdpProxy(local);
+			_proxy = proxy ?? new NoneUdpProxy(local);
 
-			Server = server;
-			Port = port;
+			_remoteEndPoint = new IPEndPoint(server, port);
 
-			Timeout = TimeSpan.FromSeconds(3);
 			Status.LocalEndPoint = local;
 		}
 
-		public async Task QueryAsync()
+		public virtual async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
 		{
-			try
-			{
-				Status.Reset();
-				using var cts = new CancellationTokenSource(Timeout);
-				await Proxy.ConnectAsync(cts.Token);
+			using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+			cts.CancelAfter(ReceiveTimeout);
 
-				await FilteringBehaviorTestBaseAsync(cts.Token);
-				if (Status.BindingTestResult != BindingTestResult.Success
-					|| Status.FilteringBehavior == FilteringBehavior.UnsupportedServer
-				)
-				{
-					return;
-				}
+			await _proxy.ConnectAsync(cts.Token);
+		}
 
-				if (Equals(Status.PublicEndPoint, Status.LocalEndPoint))
-				{
-					Status.MappingBehavior = MappingBehavior.Direct;
-					return;
-				}
+		public virtual async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
+		{
+			await _proxy.CloseAsync(cancellationToken);
+		}
 
-				// MappingBehaviorTest test II
-				var (success2, result2) = await MappingBehaviorTestBase2Async(cts.Token);
-				if (!success2)
-				{
-					return;
-				}
+		public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
+		{
+			Status.Reset();
 
-				// MappingBehaviorTest test III
-				await MappingBehaviorTestBase3Async(result2, cts.Token);
+			await FilteringBehaviorTestBaseAsync(cancellationToken);
+			if (Status.BindingTestResult != BindingTestResult.Success
+				|| Status.FilteringBehavior == FilteringBehavior.UnsupportedServer
+			)
+			{
+				return;
 			}
-			finally
+
+			if (Equals(Status.PublicEndPoint, Status.LocalEndPoint))
 			{
-				await Proxy.DisconnectAsync();
+				Status.MappingBehavior = MappingBehavior.Direct;
+				return;
+			}
+
+			// MappingBehaviorTest test II
+			var (success2, result2) = await MappingBehaviorTestBase2Async(cancellationToken);
+			if (!success2)
+			{
+				return;
 			}
+
+			// MappingBehaviorTest test III
+			await MappingBehaviorTestBase3Async(result2, cancellationToken);
 		}
 
 		public async Task BindingTestAsync()
@@ -93,19 +89,19 @@ namespace STUN.Client
 			try
 			{
 				Status.Reset();
-				using var cts = new CancellationTokenSource(Timeout);
-				await Proxy.ConnectAsync(cts.Token);
+				using var cts = new CancellationTokenSource(ReceiveTimeout);
+				await _proxy.ConnectAsync(cts.Token);
 				await BindingTestInternalAsync(cts.Token);
 			}
 			finally
 			{
-				await Proxy.DisconnectAsync();
+				await _proxy.ConnectAsync();
 			}
 		}
 
 		private async Task BindingTestInternalAsync(CancellationToken token)
 		{
-			Status.Clone(await BindingTestBaseAsync(RemoteEndPoint, token));
+			Status.Clone(await BindingTestBaseAsync(_remoteEndPoint, token));
 		}
 
 		private async Task<StunResult5389> BindingTestBaseAsync(IPEndPoint remote, CancellationToken token)
@@ -142,8 +138,8 @@ namespace STUN.Client
 			try
 			{
 				Status.Reset();
-				using var cts = new CancellationTokenSource(Timeout);
-				await Proxy.ConnectAsync(cts.Token);
+				using var cts = new CancellationTokenSource(ReceiveTimeout);
+				await _proxy.ConnectAsync(cts.Token);
 
 				// test I
 				await BindingTestInternalAsync(cts.Token);
@@ -153,8 +149,8 @@ namespace STUN.Client
 				}
 
 				if (Status.OtherEndPoint is null
-					|| Equals(Status.OtherEndPoint.Address, RemoteEndPoint.Address)
-					|| Status.OtherEndPoint.Port == RemoteEndPoint.Port)
+					|| Equals(Status.OtherEndPoint.Address, _remoteEndPoint.Address)
+					|| Status.OtherEndPoint.Port == _remoteEndPoint.Port)
 				{
 					Status.MappingBehavior = MappingBehavior.UnsupportedServer;
 					return;
@@ -178,13 +174,13 @@ namespace STUN.Client
 			}
 			finally
 			{
-				await Proxy.DisconnectAsync();
+				await _proxy.CloseAsync();
 			}
 		}
 
 		private async Task<(bool, StunResult5389)> MappingBehaviorTestBase2Async(CancellationToken token)
 		{
-			var result2 = await BindingTestBaseAsync(new IPEndPoint(Status.OtherEndPoint!.Address, RemoteEndPoint.Port), token);
+			var result2 = await BindingTestBaseAsync(new IPEndPoint(Status.OtherEndPoint!.Address, _remoteEndPoint.Port), token);
 			if (result2.BindingTestResult != BindingTestResult.Success)
 			{
 				Status.MappingBehavior = MappingBehavior.Fail;
@@ -222,8 +218,8 @@ namespace STUN.Client
 			}
 
 			if (Status.OtherEndPoint is null
-				|| Equals(Status.OtherEndPoint.Address, RemoteEndPoint.Address)
-				|| Status.OtherEndPoint.Port == RemoteEndPoint.Port)
+				|| Equals(Status.OtherEndPoint.Address, _remoteEndPoint.Address)
+				|| Status.OtherEndPoint.Port == _remoteEndPoint.Port)
 			{
 				Status.FilteringBehavior = FilteringBehavior.UnsupportedServer;
 				return;
@@ -235,7 +231,7 @@ namespace STUN.Client
 				StunMessageType = StunMessageType.BindingRequest,
 				Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
 			};
-			var (response2, _, _) = await TestAsync(test2, RemoteEndPoint, Status.OtherEndPoint, token);
+			var (response2, _, _) = await TestAsync(test2, _remoteEndPoint, Status.OtherEndPoint, token);
 
 			if (response2 is not null)
 			{
@@ -249,7 +245,7 @@ namespace STUN.Client
 				StunMessageType = StunMessageType.BindingRequest,
 				Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
 			};
-			var (response3, remote3, _) = await TestAsync(test3, RemoteEndPoint, RemoteEndPoint, token);
+			var (response3, remote3, _) = await TestAsync(test3, _remoteEndPoint, _remoteEndPoint, token);
 
 			if (response3 is null || remote3 is null)
 			{
@@ -257,7 +253,7 @@ namespace STUN.Client
 				return;
 			}
 
-			if (Equals(remote3.Address, RemoteEndPoint.Address) && remote3.Port != RemoteEndPoint.Port)
+			if (Equals(remote3.Address, _remoteEndPoint.Address) && remote3.Port != _remoteEndPoint.Port)
 			{
 				Status.FilteringBehavior = FilteringBehavior.AddressAndPortDependent;
 			}
@@ -272,44 +268,34 @@ namespace STUN.Client
 			try
 			{
 				Status.Reset();
-				using var cts = new CancellationTokenSource(Timeout);
-				await Proxy.ConnectAsync(cts.Token);
+				using var cts = new CancellationTokenSource(ReceiveTimeout);
+				await _proxy.ConnectAsync(cts.Token);
 				await FilteringBehaviorTestBaseAsync(cts.Token);
 			}
 			finally
 			{
-				await Proxy.DisconnectAsync();
+				await _proxy.CloseAsync();
 			}
 		}
 
-		private async Task<(StunMessage5389?, IPEndPoint?, IPAddress?)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken token)
+		private async Task<(StunMessage5389?, IPEndPoint?, IPAddress?)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
 		{
 			try
 			{
-				using var memoryOwner = MemoryPool<byte>.Shared.Rent(ushort.MaxValue);
-				var sendBuffer = memoryOwner.Memory;
-				var length = sendMessage.WriteTo(sendBuffer.Span);
-				//var t = DateTime.Now;
-
-				// Simple retransmissions
-				//https://tools.ietf.org/html/rfc3489#section-9.3
-				//while (t + TimeSpan.FromSeconds(3) > DateTime.Now)
+				using var memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
+				var buffer = memoryOwner.Memory;
+				var length = sendMessage.WriteTo(buffer.Span);
+
+				await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
+
+				using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+				cts.CancelAfter(ReceiveTimeout);
+				var r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
+
+				var message = new StunMessage5389();
+				if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
 				{
-					try
-					{
-						var (receive1, ipe, local) = await Proxy.ReceiveAsync(sendBuffer[..length], remote, receive, token);
-
-						var message = new StunMessage5389();
-						if (message.TryParse(receive1) &&
-							message.IsSameTransaction(sendMessage))
-						{
-							return (message, ipe, local);
-						}
-					}
-					catch (Exception ex)
-					{
-						Debug.WriteLine(ex);
-					}
+					return (message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
 				}
 			}
 			catch (Exception ex)
@@ -322,7 +308,7 @@ namespace STUN.Client
 
 		public void Dispose()
 		{
-			Proxy.Dispose();
+			_proxy.Dispose();
 		}
 	}
 }

+ 6 - 5
STUN/Proxy/IUdpProxy.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Net;
+using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -7,10 +8,10 @@ namespace STUN.Proxy
 {
 	public interface IUdpProxy : IDisposable
 	{
-		TimeSpan Timeout { get; set; }
-		IPEndPoint LocalEndPoint { get; }
-		Task ConnectAsync(CancellationToken token = default);
-		Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(ReadOnlyMemory<byte> bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default);
-		Task DisconnectAsync();
+		Socket Client { get; }
+		ValueTask ConnectAsync(CancellationToken cancellationToken = default);
+		ValueTask CloseAsync(CancellationToken cancellationToken = default);
+		ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default);
+		ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
 	}
 }

+ 18 - 27
STUN/Proxy/NoneUdpProxy.cs

@@ -1,7 +1,6 @@
+using Microsoft;
 using STUN.Utils;
 using System;
-using System.Diagnostics;
-using System.Linq;
 using System.Net;
 using System.Net.Sockets;
 using System.Threading;
@@ -11,48 +10,40 @@ namespace STUN.Proxy
 {
 	public class NoneUdpProxy : IUdpProxy
 	{
-		public TimeSpan Timeout
-		{
-			get => TimeSpan.FromMilliseconds(_udpClient.Client.ReceiveTimeout);
-			set => _udpClient.Client.ReceiveTimeout = Convert.ToInt32(value.TotalMilliseconds);
-		}
+		public Socket Client { get; }
 
-		public IPEndPoint LocalEndPoint => (IPEndPoint)_udpClient.Client.LocalEndPoint!;
+		public NoneUdpProxy(IPEndPoint localEndPoint)
+		{
+			Requires.NotNull(localEndPoint, nameof(localEndPoint));
 
-		private readonly UdpClient _udpClient;
+			Client = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
+			Client.Bind(localEndPoint);
+		}
 
-		public NoneUdpProxy(IPEndPoint? local)
+		public ValueTask ConnectAsync(CancellationToken cancellationToken = default)
 		{
-			_udpClient = local is null ? new UdpClient() : new UdpClient(local);
+			return default;
 		}
 
-		public Task ConnectAsync(CancellationToken token = default)
+		public ValueTask CloseAsync(CancellationToken cancellationToken = default)
 		{
-			return Task.CompletedTask;
+			return default;
 		}
 
-		public Task DisconnectAsync()
+		public ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default)
 		{
-			return Task.CompletedTask;
+			return Client.ReceiveMessageFromAsync(buffer, socketFlags, remoteEndPoint, cancellationToken);
 		}
 
-		public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(ReadOnlyMemory<byte> bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
+		public ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default)
 		{
-			Debug.WriteLine($@"{LocalEndPoint} => {remote} {bytes.Length} 字节");
-
-			//TODO .NET6.0
-			var buffer = bytes.ToArray();
-			await _udpClient.SendAsync(buffer, buffer.Length, remote);
-
-			var res = new byte[ushort.MaxValue];
-
-			var (local, length, rec) = await _udpClient.Client.ReceiveMessageFromAsync(receive, res, SocketFlags.None);
-			return (res.Take(length).ToArray(), rec, local);
+			return Client.SendToAsync(buffer, socketFlags, remoteEP, cancellationToken);
 		}
 
 		public void Dispose()
 		{
-			_udpClient.Dispose();
+			Client.Dispose();
+			GC.SuppressFinalize(this);
 		}
 	}
 }

+ 5 - 5
STUN/Proxy/ProxyFactory.cs

@@ -1,4 +1,5 @@
 using Microsoft;
+using Socks5.Models;
 using STUN.Enums;
 using System.Net;
 
@@ -6,9 +7,7 @@ namespace STUN.Proxy
 {
 	public static class ProxyFactory
 	{
-		public static IUdpProxy CreateProxy(
-			ProxyType type, IPEndPoint? local,
-			IPEndPoint? proxy = default, string? user = default, string? password = default)
+		public static IUdpProxy CreateProxy(ProxyType type, IPEndPoint local, Socks5CreateOption option)
 		{
 			switch (type)
 			{
@@ -18,8 +17,9 @@ namespace STUN.Proxy
 				}
 				case ProxyType.Socks5:
 				{
-					Requires.Argument(proxy is not null, nameof(proxy), @"Proxy server is null");
-					return new Socks5UdpProxy(local, proxy, user, password);
+					Requires.NotNull(option, nameof(option));
+					Requires.Argument(option.Address is not null, nameof(option), @"Proxy server is null");
+					return new Socks5UdpProxy(local, option);
 				}
 				default:
 				{

+ 72 - 206
STUN/Proxy/Socks5UdpProxy.cs

@@ -1,10 +1,15 @@
+using Microsoft;
+using Socks5.Clients;
+using Socks5.Enums;
+using Socks5.Models;
+using Socks5.Utils;
 using STUN.Utils;
 using System;
-using System.Diagnostics;
+using System.Buffers;
+using System.IO;
 using System.Net;
-using System.Net.NetworkInformation;
 using System.Net.Sockets;
-using System.Text;
+using System.Runtime.CompilerServices;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -12,254 +17,115 @@ namespace STUN.Proxy
 {
 	public class Socks5UdpProxy : IUdpProxy
 	{
-		private readonly TcpClient _assoc;
-		private readonly IPEndPoint _socksTcpEndPoint;
-
-		private IPEndPoint? _assocEndPoint;
-
-		public TimeSpan Timeout
+		public Socket Client
 		{
-			get => TimeSpan.FromMilliseconds(_udpClient.Client.ReceiveTimeout);
-			set
+			[MethodImpl(MethodImplOptions.AggressiveInlining)]
+			get
 			{
-				var timeout = Convert.ToInt32(value.TotalMilliseconds);
-				_udpClient.Client.ReceiveTimeout = timeout;
-				_assoc.ReceiveTimeout = timeout;
+				Verify.Operation(_socks5Client?.UdpClient is not null, @"Socks5 is not established.");
+				return _socks5Client.UdpClient;
 			}
 		}
 
-		public IPEndPoint LocalEndPoint => (IPEndPoint)_udpClient.Client.LocalEndPoint!;
+		private readonly Socks5CreateOption _socks5Options;
+		private readonly IPEndPoint _localEndPoint;
 
-		private readonly UdpClient _udpClient;
+		private Socks5Client? _socks5Client;
+		private ServerBound _udpServerBound;
 
-		private readonly string? _user;
-		private readonly string? _password;
-
-		public Socks5UdpProxy(IPEndPoint? local, IPEndPoint proxy)
+		public Socks5UdpProxy(IPEndPoint localEndPoint, Socks5CreateOption socks5Options)
 		{
-			_udpClient = local is null ? new UdpClient() : new UdpClient(local);
-			_assoc = new TcpClient(proxy.AddressFamily);
-			_socksTcpEndPoint = proxy;
+			_socks5Options = socks5Options;
+			Requires.NotNull(localEndPoint, nameof(localEndPoint));
+			Requires.NotNull(socks5Options, nameof(socks5Options));
+			Requires.Argument(socks5Options.Address is not null, nameof(socks5Options), @"SOCKS5 address is null");
+
+			_localEndPoint = localEndPoint;
+			_socks5Options = socks5Options;
 		}
 
-		public Socks5UdpProxy(IPEndPoint? local, IPEndPoint proxy, string? user, string? password) : this(local, proxy)
+		public async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
 		{
-			_user = user;
-			_password = password;
+			Verify.Operation(_socks5Client?.Status is not Status.Established, @"SOCKS5 client has been connected");
+			_socks5Client?.Dispose();
+
+			_socks5Client = new Socks5Client(_socks5Options);
+			_udpServerBound = await _socks5Client.UdpAssociateAsync(_localEndPoint.Address, (ushort)_localEndPoint.Port, cancellationToken);
 		}
 
-		public async Task ConnectAsync(CancellationToken token = default)
+		public ValueTask CloseAsync(CancellationToken cancellationToken = default)
 		{
-			try
+			if (_socks5Client is not null)
 			{
-				var buf = new byte[1024];
-				await _assoc.ConnectAsync(_socksTcpEndPoint.Address, _socksTcpEndPoint.Port, token);
-				var s = _assoc.GetStream();
-				using var _ = token.Register(() => s.Close());
-				var requestPasswordAuth = !string.IsNullOrEmpty(_user);
-
-				#region Handshake
-
-				// we have no GSS-API support
-				var handShake = requestPasswordAuth ? new byte[] { 5, 2, 0, 2 } : new byte[] { 5, 1, 0 };
-				await s.WriteAsync(handShake, 0, handShake.Length, token);
-
-				// 5 auth(ff=deny)
-				if (await s.ReadAsync(buf, 0, 2, token) != 2)
-				{
-					throw new ProtocolViolationException();
-				}
-
-				if (buf[0] != 5)
-				{
-					throw new ProtocolViolationException();
-				}
-
-				#endregion
-
-				#region Auth
-
-				var auth = buf[1];
-				switch (auth)
-				{
-					case 0:
-						break;
-					case 2:
-						var uByte = Encoding.UTF8.GetBytes(_user ?? string.Empty);
-						var pByte = Encoding.UTF8.GetBytes(_password ?? string.Empty);
-						buf[0] = 1;
-						buf[1] = (byte)uByte.Length;
-						Array.Copy(uByte, 0, buf, 2, uByte.Length);
-						buf[uByte.Length + 2] = (byte)pByte.Length;
-						Array.Copy(pByte, 0, buf, uByte.Length + 3, pByte.Length);
-						// 1 userLen user passLen pass
-						await s.WriteAsync(buf, 0, uByte.Length + pByte.Length + 3, token);
-						// 1 state(0=ok)
-						if (await s.ReadAsync(buf, 0, 2, token) != 2)
-						{
-							throw new ProtocolViolationException();
-						}
-
-						if (buf[0] != 1)
-						{
-							throw new ProtocolViolationException();
-						}
-
-						if (buf[1] != 0)
-						{
-							throw new UnauthorizedAccessException();
-						}
-
-						break;
-					case 0xff:
-						throw new UnauthorizedAccessException();
-					default:
-						throw new ProtocolViolationException();
-				}
-
-				#endregion
-
-				#region UDP Assoc Send
-
-				buf[0] = 5;
-				buf[1] = 3;
-				buf[2] = 0;
-
-				var abyte = GetEndPointByte(new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort));
-				var addrLen = abyte.Length;
-				Array.Copy(abyte, 0, buf, 3, addrLen);
-				// 5 cmd(3=udpassoc) 0 atyp(1=v4 3=dns 4=v5) addr port
-				await s.WriteAsync(buf, 0, addrLen + 3, token);
-
-				#endregion
-
-				#region UDP Assoc Response
+				_socks5Client.Dispose();
+				_socks5Client = null;
+			}
+			return default;
+		}
 
-				if (await s.ReadAsync(buf, 0, 4, token) != 4)
-				{
-					throw new ProtocolViolationException();
-				}
+		public async ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default)
+		{
+			Verify.Operation(_socks5Client?.Status is Status.Established && _socks5Client.UdpClient is not null, @"Socks5 is not established.");
 
-				if (buf[0] != 5 || buf[2] != 0)
+			var t = ArrayPool<byte>.Shared.Rent(buffer.Length);
+			try
+			{
+				if (_udpServerBound.Type is AddressType.Domain)
 				{
-					throw new ProtocolViolationException();
+					ThrowErrorAddressType();
 				}
 
-				if (buf[1] != 0)
-				{
-					throw new UnauthorizedAccessException();
-				}
+				var remote = new IPEndPoint(_udpServerBound.Address!, _udpServerBound.Port);
+				var r = await _socks5Client.UdpClient.ReceiveMessageFromAsync(t, socketFlags, remote, cancellationToken);
+				var u = Unpack.Udp(t.AsMemory(0, r.ReceivedBytes));
 
-				addrLen = GetAddressLength(buf[3]);
+				u.Data.CopyTo(buffer);
 
-				var addr = new byte[addrLen];
-				if (await s.ReadAsync(addr, 0, addrLen, token) != addrLen)
+				if (u.Type is AddressType.Domain)
 				{
-					throw new ProtocolViolationException();
+					ThrowErrorAddressType();
 				}
 
-				var assocIP = new IPAddress(addr);
-				if (await s.ReadAsync(buf, 0, 2, token) != 2)
+				return new SocketReceiveMessageFromResult
 				{
-					throw new ProtocolViolationException();
-				}
-
-				var assocPort = buf[0] * 256 + buf[1];
-
-				#endregion
-
-				_assocEndPoint = new IPEndPoint(assocIP, assocPort);
+					ReceivedBytes = u.Data.Length,
+					SocketFlags = r.SocketFlags,
+					RemoteEndPoint = new IPEndPoint(u.Address!, u.Port),
+					PacketInformation = r.PacketInformation
+				};
 			}
-			catch (ObjectDisposedException ex) when (ex.ObjectName == typeof(NetworkStream).FullName)
+			finally
 			{
-				await DisconnectAsync();
-				throw new TimeoutException();
+				ArrayPool<byte>.Shared.Return(t);
 			}
-			catch (Exception e)
+
+			static void ThrowErrorAddressType()
 			{
-				Debug.WriteLine(e);
-				await DisconnectAsync();
-				throw;
+				throw new InvalidDataException(@"Received error AddressType");
 			}
 		}
 
-		public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(ReadOnlyMemory<byte> bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
+		public async ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default)
 		{
-			var state = _assoc.GetState();
-			if (state != TcpState.Established)
-			{
-				throw new InvalidOperationException(@"No UDP association, maybe already disconnected or not connected");
-			}
-
-			var remoteBytes = GetEndPointByte(remote);
-			var proxyBytes = new byte[bytes.Length + remoteBytes.Length + 3];
-			Array.Copy(remoteBytes, 0, proxyBytes, 3, remoteBytes.Length);
-			bytes.Span.CopyTo(proxyBytes.AsSpan(remoteBytes.Length + 3));
-
-			await _udpClient.SendAsync(proxyBytes, proxyBytes.Length, _assocEndPoint);
-			var res = new byte[ushort.MaxValue];
-
-			var (local, length, _) = await _udpClient.Client.ReceiveMessageFromAsync(_assocEndPoint!, res, SocketFlags.None);
+			Verify.Operation(_socks5Client is not null, @"SOCKS5 client is not connected");
 
-			if (res[0] != 0 || res[1] != 0 || res[2] != 0)
+			if (remoteEP is not IPEndPoint remote)
 			{
-				throw new Exception();
+				ThrowNotSupportedException();
 			}
 
-			var addressLen = GetAddressLength(res[3]);
+			return await _socks5Client.SendUdpAsync(buffer, remote.Address, (ushort)remote.Port, cancellationToken);
 
-			var ipByte = new byte[addressLen];
-			Array.Copy(res, 4, ipByte, 0, addressLen);
-
-			var ip = new IPAddress(ipByte);
-			var port = res[addressLen + 4] * 256 + res[addressLen + 5];
-			var ret = new byte[length - addressLen - 6];
-			Array.Copy(res, addressLen + 6, ret, 0, length - addressLen - 6);
-			return (
-				ret,
-				new IPEndPoint(ip, port),
-				local);
-		}
-
-		public Task DisconnectAsync()
-		{
-			try
-			{
-				_assoc.Close();
-			}
-			catch
+			static void ThrowNotSupportedException()
 			{
-				// ignored
+				throw new NotSupportedException();
 			}
-
-			return Task.CompletedTask;
-		}
-
-		private static byte[] GetEndPointByte(IPEndPoint ep)
-		{
-			var ipByte = ep.Address.GetAddressBytes();
-			var ret = new byte[ipByte.Length + 3];
-			ret[0] = ipByte.Length == 4 ? (byte)1 : (byte)4;
-			Array.Copy(ipByte, 0, ret, 1, ipByte.Length);
-			ret[ipByte.Length + 1] = (byte)(ep.Port / 256);
-			ret[ipByte.Length + 2] = (byte)(ep.Port % 256);
-			return ret;
-		}
-
-		private static int GetAddressLength(byte b)
-		{
-			return b switch
-			{
-				1 => 4,
-				4 => 16,
-				_ => throw new NotSupportedException()
-			};
 		}
 
 		public void Dispose()
 		{
-			_udpClient.Dispose();
-			_assoc.Dispose();
+			_socks5Client?.Dispose();
+			GC.SuppressFinalize(this);
 		}
 	}
 }

+ 1 - 0
STUN/STUN.csproj

@@ -17,6 +17,7 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.VisualStudio.Validation" Version="16.10.34" />
+    <PackageReference Include="Socks5" Version="0.9.7" />
   </ItemGroup>
 
 </Project>

+ 7 - 0
STUN/StunResult/ClassicStunResult.cs

@@ -6,6 +6,13 @@ namespace STUN.StunResult
 	{
 		public NatType NatType { get; set; } = NatType.Unknown;
 
+		public void Clone(ClassicStunResult result)
+		{
+			PublicEndPoint = result.PublicEndPoint;
+			LocalEndPoint = result.LocalEndPoint;
+			NatType = result.NatType;
+		}
+
 		public override void Reset()
 		{
 			base.Reset();

+ 55 - 0
STUN/Utils/NetUtils.cs

@@ -1,8 +1,12 @@
+using System;
+using System.Buffers;
 using System.Diagnostics;
 using System.Linq;
 using System.Net;
 using System.Net.NetworkInformation;
 using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
 using System.Threading.Tasks;
 
 namespace STUN.Utils
@@ -31,5 +35,56 @@ namespace STUN.Utils
 				return (local, length, (IPEndPoint)receive);
 			});
 		}
+
+		//TODO Remove in .NET6.0
+		public static ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(this Socket client, Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default)
+		{
+			client.ReceiveTimeout = (int)TimeSpan.FromSeconds(3).TotalMilliseconds;
+			return new ValueTask<SocketReceiveMessageFromResult>(Task.Run(() =>
+			{
+				if (!MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)buffer, out var segment))
+				{
+					ThrowNotSupportedException();
+				}
+
+				var length = client.ReceiveMessageFrom(segment.Array!, segment.Offset, segment.Count, ref socketFlags, ref remoteEndPoint, out var ipPacketInformation);
+				return new SocketReceiveMessageFromResult
+				{
+					ReceivedBytes = length,
+					SocketFlags = socketFlags,
+					RemoteEndPoint = remoteEndPoint,
+					PacketInformation = ipPacketInformation
+				};
+			}, cancellationToken));
+
+			static void ThrowNotSupportedException()
+			{
+				throw new NotSupportedException();
+			}
+		}
+
+		//TODO Remove in .NET6.0
+		public static async ValueTask<int> SendToAsync(this Socket client, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken)
+		{
+			byte[]? t = null;
+			try
+			{
+				if (!MemoryMarshal.TryGetArray(buffer, out var segment))
+				{
+					t = ArrayPool<byte>.Shared.Rent(buffer.Length);
+					buffer.CopyTo(t);
+					segment = new ArraySegment<byte>(t, 0, buffer.Length);
+				}
+
+				return await client.SendToAsync(segment, socketFlags, remoteEP);
+			}
+			finally
+			{
+				if (t is not null)
+				{
+					ArrayPool<byte>.Shared.Return(t);
+				}
+			}
+		}
 	}
 }

+ 9 - 9
UnitTest/StunClient3489Test.cs

@@ -36,7 +36,7 @@ namespace UnitTest
 		[TestMethod]
 		public async Task UdpBlockedTestAsync()
 		{
-			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, null, null);
+			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, Any, null);
 			var client = mock.Object;
 
 			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync((StunResponse?)null);
@@ -49,7 +49,7 @@ namespace UnitTest
 		[TestMethod]
 		public async Task UnsupportedServerTestAsync()
 		{
-			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, null, null);
+			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, Any, null);
 			var client = mock.Object;
 
 			mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
@@ -111,7 +111,7 @@ namespace UnitTest
 		[TestMethod]
 		public async Task NoNatTestAsync()
 		{
-			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, null, null);
+			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, Any, null);
 			var client = mock.Object;
 
 			var openInternetTest1Response = new StunResponse(
@@ -158,7 +158,7 @@ namespace UnitTest
 		[TestMethod]
 		public async Task FullConeTestAsync()
 		{
-			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, null, null);
+			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, Any, null);
 			var client = mock.Object;
 
 			var test1Response = new StunResponse(
@@ -248,7 +248,7 @@ namespace UnitTest
 		[TestMethod]
 		public async Task SymmetricTestAsync()
 		{
-			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, null, null);
+			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, Any, null);
 			var client = mock.Object;
 
 			var test1Response = new StunResponse(
@@ -295,7 +295,7 @@ namespace UnitTest
 		[TestMethod]
 		public async Task RestrictedConeTestAsync()
 		{
-			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, null, null);
+			var mock = new Mock<StunClient3489>(IPAddress.Any, Port, Any, null);
 			var client = mock.Object;
 
 			var test1Response = new StunResponse(
@@ -361,7 +361,7 @@ namespace UnitTest
 		public async Task Test1Async()
 		{
 			var ip = await _dnsClient.QueryAsync(Server);
-			using var client = new StunClient3489(ip);
+			using var client = new StunClient3489(ip, 3478, Any);
 
 			// test I
 			var response1 = await client.Test1Async(default);
@@ -394,7 +394,7 @@ namespace UnitTest
 		public async Task Test2Async()
 		{
 			var ip = await _dnsClient.QueryAsync(Server);
-			using var client = new StunClient3489(ip);
+			using var client = new StunClient3489(ip, 3478, Any);
 			var response2 = await client.Test2Async(ip.AddressFamily is AddressFamily.InterNetworkV6 ? IPv6Any : Any, default);
 
 			Assert.IsNotNull(response2);
@@ -409,7 +409,7 @@ namespace UnitTest
 		public async Task Test3Async()
 		{
 			var ip = await _dnsClient.QueryAsync(Server);
-			using var client = new StunClient3489(ip);
+			using var client = new StunClient3489(ip, 3478, Any);
 			var response = await client.Test3Async(default);
 
 			Assert.IsNotNull(response);