Browse Source

feat: Add tls support

Bruce Wayne 2 years ago
parent
commit
9118a38240

+ 1 - 1
NatTypeTester.Models/Config.cs

@@ -8,7 +8,7 @@ namespace NatTypeTester.Models;
 [UsedImplicitly]
 public record Config : ReactiveRecord, ISingletonDependency
 {
-	private string _stunServer = @"stun.hot-chilli.net";
+	private string _stunServer = @"stunserver.stunprotocol.org";
 	public string StunServer
 	{
 		get => _stunServer;

+ 2 - 1
NatTypeTester.ViewModels/MainWindowViewModel.cs

@@ -21,8 +21,9 @@ public class MainWindowViewModel : ViewModelBase, IScreen
 
 	private readonly IEnumerable<string> _defaultServers = new HashSet<string>
 	{
-		@"stun.hot-chilli.net",
 		@"stunserver.stunprotocol.org",
+		@"stun.fitauto.ru",
+		@"stun.hot-chilli.net",
 		@"stun.syncthing.net",
 		@"stun.qq.com",
 		@"stun.miwifi.com"

+ 1 - 0
STUN/Client/IStunClient5389.cs

@@ -7,4 +7,5 @@ public interface IStunClient5389 : IStunClient
 	StunResult5389 State { get; }
 	ValueTask<StunResult5389> BindingTestAsync(CancellationToken cancellationToken = default);
 	ValueTask MappingBehaviorTestAsync(CancellationToken cancellationToken = default);
+	ValueTask FilteringBehaviorTestAsync(CancellationToken cancellationToken = default);
 }

+ 5 - 0
STUN/Client/StunClient5389TCP.cs

@@ -111,6 +111,11 @@ public class StunClient5389TCP : IStunClient5389
 		}
 	}
 
+	public ValueTask FilteringBehaviorTestAsync(CancellationToken cancellationToken = default)
+	{
+		throw new NotSupportedException(@"Filtering test applies only to UDP.");
+	}
+
 	public async ValueTask<StunResult5389> BindingTestAsync(CancellationToken cancellationToken = default)
 	{
 		return await BindingTestBaseAsync(_remoteEndPoint, cancellationToken);

+ 2 - 2
STUN/Client/StunClient5389UDP.cs

@@ -86,7 +86,7 @@ public class StunClient5389UDP : IStunClient5389, IUdpStunClient
 		return await BindingTestBaseAsync(_remoteEndPoint, cancellationToken);
 	}
 
-	public virtual async ValueTask<StunResult5389> BindingTestBaseAsync(IPEndPoint remote, CancellationToken cancellationToken = default)
+	protected virtual async ValueTask<StunResult5389> BindingTestBaseAsync(IPEndPoint remote, CancellationToken cancellationToken = default)
 	{
 		StunResult5389 result = new();
 		StunMessage5389 test = new()
@@ -234,7 +234,7 @@ public class StunClient5389UDP : IStunClient5389, IUdpStunClient
 		}
 	}
 
-	public virtual async ValueTask<StunResponse?> FilteringBehaviorTest2Async(CancellationToken cancellationToken = default)
+	protected virtual async ValueTask<StunResponse?> FilteringBehaviorTest2Async(CancellationToken cancellationToken = default)
 	{
 		Assumes.NotNull(State.OtherEndPoint);
 

+ 11 - 11
STUN/Proxy/DirectTcpProxy.cs

@@ -13,13 +13,13 @@ public class DirectTcpProxy : ITcpProxy, IDisposableObservable
 		get
 		{
 			Verify.NotDisposed(this);
-			return _tcpClient?.Client.LocalEndPoint as IPEndPoint;
+			return TcpClient?.Client.LocalEndPoint as IPEndPoint;
 		}
 	}
 
-	private TcpClient? _tcpClient;
+	protected TcpClient? TcpClient;
 
-	public async ValueTask<IDuplexPipe> ConnectAsync(IPEndPoint local, IPEndPoint dst, CancellationToken cancellationToken = default)
+	public virtual async ValueTask<IDuplexPipe> ConnectAsync(IPEndPoint local, IPEndPoint dst, CancellationToken cancellationToken = default)
 	{
 		Verify.NotDisposed(this);
 		Requires.NotNull(local, nameof(local));
@@ -27,10 +27,10 @@ public class DirectTcpProxy : ITcpProxy, IDisposableObservable
 
 		await CloseAsync(cancellationToken);
 
-		_tcpClient = new TcpClient(local) { NoDelay = true };
-		await _tcpClient.ConnectAsync(dst, cancellationToken);
+		TcpClient = new TcpClient(local) { NoDelay = true };
+		await TcpClient.ConnectAsync(dst, cancellationToken);
 
-		return _tcpClient.Client.AsDuplexPipe();
+		return TcpClient.Client.AsDuplexPipe();
 	}
 
 	public ValueTask CloseAsync(CancellationToken cancellationToken = default)
@@ -42,21 +42,21 @@ public class DirectTcpProxy : ITcpProxy, IDisposableObservable
 		return default;
 	}
 
-	private void CloseClient()
+	protected virtual void CloseClient()
 	{
-		if (_tcpClient is null)
+		if (TcpClient is null)
 		{
 			return;
 		}
 
 		try
 		{
-			_tcpClient.Client.Close(0);
+			TcpClient.Client.Close(0);
 		}
 		finally
 		{
-			_tcpClient.Dispose();
-			_tcpClient = default;
+			TcpClient.Dispose();
+			TcpClient = default;
 		}
 	}
 

+ 13 - 13
STUN/Proxy/Socks5TcpProxy.cs

@@ -19,19 +19,19 @@ public class Socks5TcpProxy : ITcpProxy, IDisposableObservable
 		}
 	}
 
-	private readonly Socks5CreateOption _socks5Options;
+	protected readonly Socks5CreateOption Socks5Options;
 
-	private Socks5Client? _socks5Client;
+	protected Socks5Client? Socks5Client;
 
 	public Socks5TcpProxy(Socks5CreateOption socks5Options)
 	{
 		Requires.NotNull(socks5Options, nameof(socks5Options));
 		Requires.Argument(socks5Options.Address is not null, nameof(socks5Options), @"SOCKS5 address is null");
 
-		_socks5Options = socks5Options;
+		Socks5Options = socks5Options;
 	}
 
-	public async ValueTask<IDuplexPipe> ConnectAsync(IPEndPoint local, IPEndPoint dst, CancellationToken cancellationToken = default)
+	public virtual async ValueTask<IDuplexPipe> ConnectAsync(IPEndPoint local, IPEndPoint dst, CancellationToken cancellationToken = default)
 	{
 		Verify.NotDisposed(this);
 		Requires.NotNull(local, nameof(local));
@@ -39,13 +39,13 @@ public class Socks5TcpProxy : ITcpProxy, IDisposableObservable
 
 		await CloseAsync(cancellationToken);
 
-		_socks5Client = new Socks5Client(_socks5Options);
+		Socks5Client = new Socks5Client(Socks5Options);
 
 		GetTcpClient()?.Client.Bind(local);
 
-		await _socks5Client.ConnectAsync(dst.Address, (ushort)dst.Port, cancellationToken);
+		await Socks5Client.ConnectAsync(dst.Address, (ushort)dst.Port, cancellationToken);
 
-		return _socks5Client.GetPipe();
+		return Socks5Client.GetPipe();
 	}
 
 	public ValueTask CloseAsync(CancellationToken cancellationToken = default)
@@ -57,15 +57,15 @@ public class Socks5TcpProxy : ITcpProxy, IDisposableObservable
 		return default;
 	}
 
-	private TcpClient? GetTcpClient()
+	protected TcpClient? GetTcpClient()
 	{
 		// TODO
-		return _socks5Client?.GetType().GetField(@"_tcpClient", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(_socks5Client) as TcpClient;
+		return Socks5Client?.GetType().GetField(@"_tcpClient", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(Socks5Client) as TcpClient;
 	}
 
-	private void CloseClient()
+	protected virtual void CloseClient()
 	{
-		if (_socks5Client is null)
+		if (Socks5Client is null)
 		{
 			return;
 		}
@@ -76,8 +76,8 @@ public class Socks5TcpProxy : ITcpProxy, IDisposableObservable
 		}
 		finally
 		{
-			_socks5Client.Dispose();
-			_socks5Client = default;
+			Socks5Client.Dispose();
+			Socks5Client = default;
 		}
 	}
 

+ 44 - 0
STUN/Proxy/TlsProxy.cs

@@ -0,0 +1,44 @@
+using Microsoft;
+using Pipelines.Extensions;
+using System.IO.Pipelines;
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+
+namespace STUN.Proxy;
+
+public class TlsProxy : DirectTcpProxy
+{
+	private SslStream? _tlsStream;
+
+	private readonly string _targetHost;
+
+	public TlsProxy(string targetHost)
+	{
+		_targetHost = targetHost;
+	}
+
+	public override async ValueTask<IDuplexPipe> ConnectAsync(IPEndPoint local, IPEndPoint dst, CancellationToken cancellationToken = default)
+	{
+		Verify.NotDisposed(this);
+		Requires.NotNull(local, nameof(local));
+		Requires.NotNull(dst, nameof(dst));
+
+		await CloseAsync(cancellationToken);
+
+		TcpClient = new TcpClient(local) { NoDelay = true };
+		await TcpClient.ConnectAsync(dst, cancellationToken);
+
+		_tlsStream = new SslStream(TcpClient.GetStream(), true);
+
+		await _tlsStream.AuthenticateAsClientAsync(_targetHost);
+
+		return _tlsStream.AsDuplexPipe();
+	}
+
+	protected override void CloseClient()
+	{
+		_tlsStream?.Dispose();
+		base.CloseClient();
+	}
+}

+ 4 - 3
STUN/StunServer.cs

@@ -9,7 +9,8 @@ public class StunServer
 	public string Hostname { get; }
 	public ushort Port { get; }
 
-	private const ushort DefaultPort = 3478;
+	public const ushort DefaultPort = 3478;
+	public const ushort DefaultTlsPort = 5349;
 
 	public StunServer()
 	{
@@ -23,9 +24,9 @@ public class StunServer
 		Port = port;
 	}
 
-	public static bool TryParse(string s, [NotNullWhen(true)] out StunServer? result)
+	public static bool TryParse(string s, [NotNullWhen(true)] out StunServer? result, ushort defaultPort = DefaultPort)
 	{
-		if (!HostnameEndpoint.TryParse(s, out HostnameEndpoint? host, DefaultPort))
+		if (!HostnameEndpoint.TryParse(s, out HostnameEndpoint? host, defaultPort))
 		{
 			result = null;
 			return false;

+ 89 - 91
UnitTest/StunClien5389UDPTest.cs

@@ -3,6 +3,7 @@ using Dns.Net.Clients;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
 using Moq.Protected;
+using STUN;
 using STUN.Client;
 using STUN.Enums;
 using STUN.Messages;
@@ -16,9 +17,6 @@ public class StunClien5389UDPTest
 {
 	private readonly IDnsClient _dnsClient = new DefaultDnsClient();
 
-	private const string Server = @"stun.syncthing.net";
-	private const ushort Port = 3478;
-
 	private static readonly IPEndPoint Any = new(IPAddress.Any, 0);
 	private static readonly IPEndPoint LocalAddress1 = IPEndPoint.Parse(@"127.0.0.1:114");
 	private static readonly IPEndPoint MappedAddress1 = IPEndPoint.Parse(@"1.1.1.1:114");
@@ -33,8 +31,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task BindingTestSuccessAsync()
 	{
-		IPAddress ip = await _dnsClient.QueryAsync(Server);
-		using StunClient5389UDP client = new(new IPEndPoint(ip, Port), Any);
+		IPAddress ip = await _dnsClient.QueryAsync(@"stunserver.stunprotocol.org");
+		using StunClient5389UDP client = new(new IPEndPoint(ip, StunServer.DefaultPort), Any);
 
 		StunResult5389 response = await client.BindingTestAsync();
 
@@ -50,7 +48,7 @@ public class StunClien5389UDPTest
 	public async Task BindingTestFailAsync()
 	{
 		IPAddress ip = IPAddress.Parse(@"1.1.1.1");
-		using StunClient5389UDP client = new(new IPEndPoint(ip, Port), Any);
+		using StunClient5389UDP client = new(new IPEndPoint(ip, StunServer.DefaultPort), Any);
 
 		StunResult5389 response = await client.BindingTestAsync();
 
@@ -65,12 +63,12 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTestFailAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 fail = new() { BindingTestResult = BindingTestResult.Fail };
 
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(fail);
 
 		await client.MappingBehaviorTestAsync();
 
@@ -85,7 +83,7 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTestUnsupportedServerAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
 		StunClient5389UDP? client = mock.Object;
 
 		StunResult5389 r1 = new()
@@ -94,7 +92,7 @@ public class StunClien5389UDPTest
 			PublicEndPoint = MappedAddress1,
 			LocalEndPoint = LocalAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
 		await TestAsync();
 
 		StunResult5389 r2 = new()
@@ -104,7 +102,7 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress2
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
 		await TestAsync();
 
 		StunResult5389 r3 = new()
@@ -114,7 +112,7 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress3
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
 		await TestAsync();
 
 		async Task TestAsync()
@@ -132,8 +130,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTestDirectAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 response = new()
 		{
@@ -143,7 +141,7 @@ public class StunClien5389UDPTest
 			OtherEndPoint = ChangedAddress1
 		};
 
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(response);
 
 		await client.MappingBehaviorTestAsync();
 
@@ -158,8 +156,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTestEndpointIndependentAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -168,7 +166,7 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
 		await client.MappingBehaviorTestAsync();
 
 		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
@@ -182,8 +180,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTest2FailAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -197,8 +195,8 @@ public class StunClien5389UDPTest
 			BindingTestResult = BindingTestResult.Fail,
 		};
 
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
 		await client.MappingBehaviorTestAsync();
 
 		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
@@ -212,8 +210,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTestAddressDependentAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -236,9 +234,9 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress1, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
 
 		await client.MappingBehaviorTestAsync();
 
@@ -253,8 +251,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTestAddressAndPortDependentAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -277,9 +275,9 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress1, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
 
 		await client.MappingBehaviorTestAsync();
 
@@ -294,8 +292,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task MappingBehaviorTest3FailAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -315,9 +313,9 @@ public class StunClien5389UDPTest
 		{
 			BindingTestResult = BindingTestResult.Fail
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress1, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
 
 		await client.MappingBehaviorTestAsync();
 
@@ -332,12 +330,12 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task FilteringBehaviorTestFailAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 fail = new() { BindingTestResult = BindingTestResult.Fail };
 
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(fail);
 
 		await client.FilteringBehaviorTestAsync();
 
@@ -352,7 +350,7 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task FilteringBehaviorTestUnsupportedServerAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
 		StunClient5389UDP? client = mock.Object;
 
 		StunResult5389 r1 = new()
@@ -361,7 +359,7 @@ public class StunClien5389UDPTest
 			PublicEndPoint = MappedAddress1,
 			LocalEndPoint = LocalAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
 		await TestAsync();
 
 		StunResult5389 r2 = new()
@@ -371,7 +369,7 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress2
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
 		await TestAsync();
 
 		StunResult5389 r3 = new()
@@ -381,7 +379,7 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress3
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
 		await TestAsync();
 
 		async Task TestAsync()
@@ -399,8 +397,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task FilteringBehaviorTestEndpointIndependentAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -410,8 +408,8 @@ public class StunClien5389UDPTest
 			OtherEndPoint = ChangedAddress1
 		};
 		StunResponse r2 = new(DefaultStunMessage, ChangedAddress1, LocalAddress1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
 
 		await client.FilteringBehaviorTestAsync();
 
@@ -426,8 +424,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task FilteringBehaviorTest2UnsupportedServerAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -437,8 +435,8 @@ public class StunClien5389UDPTest
 			OtherEndPoint = ChangedAddress1
 		};
 		StunResponse r2 = new(DefaultStunMessage, ServerAddress, LocalAddress1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
 
 		await client.FilteringBehaviorTestAsync();
 
@@ -453,8 +451,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task FilteringBehaviorTestAddressAndPortDependentAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -463,9 +461,9 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", It.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
 
 		await client.FilteringBehaviorTestAsync();
 
@@ -480,8 +478,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task FilteringBehaviorTestAddressDependentAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -491,9 +489,9 @@ public class StunClien5389UDPTest
 			OtherEndPoint = ChangedAddress1
 		};
 		StunResponse r3 = new(DefaultStunMessage, ChangedAddress2, LocalAddress1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", It.IsAny<CancellationToken>()).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
 
 		await client.FilteringBehaviorTestAsync();
 
@@ -508,8 +506,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task FilteringBehaviorTest3UnsupportedServerAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -519,9 +517,9 @@ public class StunClien5389UDPTest
 			OtherEndPoint = ChangedAddress1
 		};
 		StunResponse r3 = new(DefaultStunMessage, ServerAddress, LocalAddress1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", It.IsAny<CancellationToken>()).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
 
 		await client.FilteringBehaviorTestAsync();
 
@@ -536,12 +534,12 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task QueryFailTestAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 fail = new() { BindingTestResult = BindingTestResult.Fail };
 
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(fail);
 
 		await client.QueryAsync();
 
@@ -556,8 +554,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task QueryUnsupportedServerTestAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -566,7 +564,7 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ServerAddress
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
 
 		await client.QueryAsync();
 
@@ -580,8 +578,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task QueryMappingBehaviorDirectTestAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -590,9 +588,9 @@ public class StunClien5389UDPTest
 			LocalEndPoint = MappedAddress1,
 			OtherEndPoint = ChangedAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", It.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
 
 		await client.QueryAsync();
 
@@ -607,8 +605,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task QueryMappingBehaviorEndpointIndependentTestAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -617,9 +615,9 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", It.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
 
 		await client.QueryAsync();
 
@@ -634,8 +632,8 @@ public class StunClien5389UDPTest
 	[TestMethod]
 	public async Task QueryMappingBehaviorAddressAndPortDependentTestAsync()
 	{
-		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default);
-		StunClient5389UDP? client = mock.Object;
+		Mock<StunClient5389UDP> mock = new(ServerAddress, Any, default!);
+		StunClient5389UDP client = mock.Object;
 
 		StunResult5389 r1 = new()
 		{
@@ -658,11 +656,11 @@ public class StunClien5389UDPTest
 			LocalEndPoint = LocalAddress1,
 			OtherEndPoint = ChangedAddress1
 		};
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
-		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", It.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress1, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest2Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
+		mock.Protected().Setup<ValueTask<StunResponse?>>(@"FilteringBehaviorTest3Async", ItExpr.IsAny<CancellationToken>()).ReturnsAsync(default(StunResponse?));
 
 		await client.QueryAsync();
 

+ 10 - 10
UnitTest/StunClient3489Test.cs

@@ -34,8 +34,8 @@ public class StunClient3489Test
 	[TestMethod]
 	public async Task UdpBlockedTestAsync()
 	{
-		Mock<StunClient3489> mock = new(Any, Any, default);
-		StunClient3489? client = mock.Object;
+		Mock<StunClient3489> mock = new(Any, Any, default!);
+		StunClient3489 client = mock.Object;
 
 		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
 
@@ -46,7 +46,7 @@ public class StunClient3489Test
 	[TestMethod]
 	public async Task UnsupportedServerTestAsync()
 	{
-		Mock<StunClient3489> mock = new(Any, Any, default);
+		Mock<StunClient3489> mock = new(Any, Any, default!);
 		StunClient3489? client = mock.Object;
 
 		mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
@@ -106,8 +106,8 @@ public class StunClient3489Test
 	[TestMethod]
 	public async Task NoNatTestAsync()
 	{
-		Mock<StunClient3489> mock = new(Any, Any, default);
-		StunClient3489? client = mock.Object;
+		Mock<StunClient3489> mock = new(Any, Any, default!);
+		StunClient3489 client = mock.Object;
 
 		StunResponse openInternetTest1Response = new(
 			new StunMessage5389
@@ -150,7 +150,7 @@ public class StunClient3489Test
 	[TestMethod]
 	public async Task FullConeTestAsync()
 	{
-		Mock<StunClient3489> mock = new(Any, Any, default);
+		Mock<StunClient3489> mock = new(Any, Any, default!);
 		StunClient3489? client = mock.Object;
 
 		StunResponse test1Response = new(
@@ -237,8 +237,8 @@ public class StunClient3489Test
 	[TestMethod]
 	public async Task SymmetricTestAsync()
 	{
-		Mock<StunClient3489> mock = new(Any, Any, default);
-		StunClient3489? client = mock.Object;
+		Mock<StunClient3489> mock = new(Any, Any, default!);
+		StunClient3489 client = mock.Object;
 
 		StunResponse test1Response = new(
 			new StunMessage5389
@@ -282,8 +282,8 @@ public class StunClient3489Test
 	[TestMethod]
 	public async Task RestrictedConeTestAsync()
 	{
-		Mock<StunClient3489> mock = new(Any, Any, default);
-		StunClient3489? client = mock.Object;
+		Mock<StunClient3489> mock = new(Any, Any, default!);
+		StunClient3489 client = mock.Object;
 
 		StunResponse test1Response = new(
 			new StunMessage5389

+ 348 - 6
UnitTest/StunClient5389TCPTest.cs

@@ -1,9 +1,12 @@
 using Dns.Net.Abstractions;
 using Dns.Net.Clients;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Moq.Protected;
 using STUN;
 using STUN.Client;
 using STUN.Enums;
+using STUN.Proxy;
 using STUN.StunResult;
 using System.Net;
 
@@ -14,16 +17,20 @@ public class StunClient5389TCPTest
 {
 	private readonly IDnsClient _dnsClient = new DefaultDnsClient();
 
-	private const string Server = @"stunserver.stunprotocol.org";
-	private const ushort Port = 3478;
-
 	private static readonly IPEndPoint Any = new(IPAddress.Any, 0);
+	private static readonly IPEndPoint LocalAddress1 = IPEndPoint.Parse(@"127.0.0.1:114");
+	private static readonly IPEndPoint MappedAddress1 = IPEndPoint.Parse(@"1.1.1.1:114");
+	private static readonly IPEndPoint MappedAddress2 = IPEndPoint.Parse(@"1.1.1.1:514");
+	private static readonly IPEndPoint ServerAddress = IPEndPoint.Parse(@"2.2.2.2:1919");
+	private static readonly IPEndPoint ChangedAddress1 = IPEndPoint.Parse(@"3.3.3.3:23333");
+	private static readonly IPEndPoint ChangedAddress2 = IPEndPoint.Parse(@"2.2.2.2:810");
+	private static readonly IPEndPoint ChangedAddress3 = IPEndPoint.Parse(@"3.3.3.3:1919");
 
 	[TestMethod]
 	public async Task BindingTestSuccessAsync()
 	{
-		IPAddress ip = await _dnsClient.QueryAsync(Server);
-		using IStunClient5389 client = new StunClient5389TCP(new IPEndPoint(ip, Port), Any);
+		IPAddress ip = await _dnsClient.QueryAsync(@"stunserver.stunprotocol.org");
+		using IStunClient5389 client = new StunClient5389TCP(new IPEndPoint(ip, StunServer.DefaultPort), Any);
 
 		StunResult5389 response = await client.BindingTestAsync();
 
@@ -39,7 +46,7 @@ public class StunClient5389TCPTest
 	public async Task BindingTestFailAsync()
 	{
 		IPAddress ip = IPAddress.Parse(@"1.1.1.1");
-		using IStunClient5389 client = new StunClient5389TCP(new IPEndPoint(ip, Port), Any);
+		using IStunClient5389 client = new StunClient5389TCP(new IPEndPoint(ip, StunServer.DefaultPort), Any);
 
 		StunResult5389 response = await client.BindingTestAsync();
 
@@ -51,6 +58,25 @@ public class StunClient5389TCPTest
 		Assert.IsNull(response.OtherEndPoint);
 	}
 
+	[TestMethod]
+	public async Task TlsBindingTestSuccessAsync()
+	{
+		Assert.IsTrue(StunServer.TryParse(@"stun.fitauto.ru", out StunServer? stunServer, StunServer.DefaultTlsPort));
+		IPAddress ip = await _dnsClient.QueryAsync(stunServer.Hostname);
+		ITcpProxy tls = new TlsProxy(stunServer.Hostname);
+		using IStunClient5389 client = new StunClient5389TCP(new IPEndPoint(ip, StunServer.DefaultPort), Any, tls);
+
+		StunResult5389 response = await client.BindingTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, response.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, response.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, response.FilteringBehavior);
+		Assert.IsNotNull(response.PublicEndPoint);
+		Assert.IsNotNull(response.LocalEndPoint);
+		Assert.IsNotNull(response.OtherEndPoint);
+	}
+
+	[Ignore]
 	[TestMethod]
 	public async Task TestServerAsync()
 	{
@@ -83,4 +109,320 @@ public class StunClient5389TCPTest
 			}
 		}
 	}
+
+	[Ignore]
+	[TestMethod]
+	public async Task TestTlsServerAsync()
+	{
+		const string url = @"https://raw.githubusercontent.com/pradt2/always-online-stun/master/valid_hosts_tcp.txt";
+		HttpClient httpClient = new();
+		string listRaw = await httpClient.GetStringAsync(url);
+		string[] list = listRaw.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+
+		foreach (string host in list)
+		{
+			if (!HostnameEndpoint.TryParse(host, out HostnameEndpoint? hostEndpoint, StunServer.DefaultTlsPort))
+			{
+				continue;
+			}
+
+			IPAddress ip = await _dnsClient.QueryAsync(hostEndpoint.Hostname);
+			ITcpProxy proxy = new TlsProxy(hostEndpoint.Hostname);
+			using IStunClient5389 client = new StunClient5389TCP(new IPEndPoint(ip, StunServer.DefaultTlsPort), Any, proxy);
+			try
+			{
+				await client.QueryAsync();
+			}
+			catch
+			{
+				continue;
+			}
+
+			if (client.State.MappingBehavior is MappingBehavior.AddressAndPortDependent or MappingBehavior.AddressDependent or MappingBehavior.EndpointIndependent or MappingBehavior.Direct)
+			{
+				Console.WriteLine(host);
+			}
+		}
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTestFailAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 fail = new() { BindingTestResult = BindingTestResult.Fail };
+
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(fail);
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Fail, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+		Assert.IsNull(client.State.PublicEndPoint);
+		Assert.IsNull(client.State.LocalEndPoint);
+		Assert.IsNull(client.State.OtherEndPoint);
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTestUnsupportedServerAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 r1 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1
+		};
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		await TestAsync();
+
+		StunResult5389 r2 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress2
+		};
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		await TestAsync();
+
+		StunResult5389 r3 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress3
+		};
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
+		await TestAsync();
+
+		return;
+
+		async Task TestAsync()
+		{
+			await client.QueryAsync();
+
+			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+			Assert.AreEqual(MappingBehavior.UnsupportedServer, client.State.MappingBehavior);
+			Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+			Assert.IsNotNull(client.State.PublicEndPoint);
+			Assert.IsNotNull(client.State.LocalEndPoint);
+		}
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTestDirectAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 response = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = MappedAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(response);
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Direct, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTestEndpointIndependentAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 r1 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ItExpr.IsAny<IPEndPoint>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.EndpointIndependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTest2FailAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 r1 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		StunResult5389 r2 = new()
+		{
+			BindingTestResult = BindingTestResult.Fail,
+		};
+
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Fail, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTestAddressDependentAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 r1 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		StunResult5389 r2 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		StunResult5389 r3 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress1, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.AddressDependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTestAddressAndPortDependentAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 r1 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		StunResult5389 r2 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		StunResult5389 r3 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress1, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.AddressAndPortDependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
+
+	[TestMethod]
+	public async Task MappingBehaviorTest3FailAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		StunResult5389 r1 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		StunResult5389 r2 = new()
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		StunResult5389 r3 = new()
+		{
+			BindingTestResult = BindingTestResult.Fail
+		};
+
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ServerAddress, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r1);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress3, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r2);
+		mock.Protected().Setup<ValueTask<StunResult5389>>(@"BindingTestBaseAsync", ChangedAddress1, ItExpr.IsAny<CancellationToken>()).ReturnsAsync(r3);
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Fail, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.None, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
+
+	[TestMethod]
+	public async Task FilteringBehaviorTestAsync()
+	{
+		Mock<StunClient5389TCP> mock = new(ServerAddress, Any, default!);
+		IStunClient5389 client = mock.Object;
+
+		await Assert.ThrowsExceptionAsync<NotSupportedException>(async () => await client.FilteringBehaviorTestAsync());
+	}
 }