Skip to content

Commit

Permalink
Refactor: RFC3489 client
Browse files Browse the repository at this point in the history
  • Loading branch information
HMBSbige committed Aug 20, 2021
1 parent 30de30e commit 73fed64
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 237 deletions.
2 changes: 1 addition & 1 deletion NatTypeTester.ViewModels/RFC3489ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private async Task TestClassicNatTypeImpl(CancellationToken token)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => this.RaisePropertyChanged(nameof(Result3489))))
{
await client.Query3489Async();
await client.QueryAsync(token);
}

Result3489.LocalEndPoint = client.LocalEndPoint;
Expand Down
204 changes: 120 additions & 84 deletions STUN/Client/StunClient3489.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,93 +19,91 @@ namespace STUN.Client
/// </summary>
public class StunClient3489 : IDisposable
{
public IPEndPoint LocalEndPoint => Proxy.LocalEndPoint;
public IPEndPoint LocalEndPoint => _proxy.LocalEndPoint;

public TimeSpan Timeout
{
get => Proxy.Timeout;
set => Proxy.Timeout = value;
get => _proxy.Timeout;
set => _proxy.Timeout = value;
}

protected readonly IPAddress Server;
protected readonly ushort Port;
private readonly IPAddress _server;
private readonly ushort _port;

protected IPEndPoint RemoteEndPoint => new(Server, Port);
private IPEndPoint RemoteEndPoint => new(_server, _port);

protected readonly IUdpProxy Proxy;
private readonly IUdpProxy _proxy;

public ClassicStunResult Status { get; } = new();

public StunClient3489(IPAddress server, ushort port = 3478, IPEndPoint? local = null, 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;
_server = server;
_port = port;

Timeout = TimeSpan.FromSeconds(1.6);
Timeout = TimeSpan.FromSeconds(3);
Status.LocalEndPoint = local;
}

private void Init()
public virtual async ValueTask ConnectAsync(CancellationToken cancellationToken)
{
Status.PublicEndPoint = default;
Status.LocalEndPoint = default;
Status.NatType = NatType.Unknown;
Status.Reset();

using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(Timeout);

await _proxy.ConnectAsync(cts.Token);
}

public virtual async ValueTask DisconnectAsync()
{
await _proxy.DisconnectAsync();
}

public async Task Query3489Async()
public async Task QueryAsync(CancellationToken cancellationToken = default)
{
try
{
Init();
using var cts = new CancellationTokenSource(Timeout);
await Proxy.ConnectAsync(cts.Token);
// test I
var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
await ConnectAsync(cancellationToken);

var (response1, remote1, local1) = await TestAsync(test1, RemoteEndPoint, RemoteEndPoint, cts.Token);
if (response1 is null || remote1 is null)
// test I
var response1 = await Test1Async(cancellationToken);
if (response1?.Message is null || response1.Remote is null)
{
Status.NatType = NatType.UdpBlocked;
return;
}

Status.LocalEndPoint = local1 is null ? null : new IPEndPoint(local1, LocalEndPoint.Port);
Status.LocalEndPoint = response1.LocalAddress is null ? null : new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);

var mappedAddress1 = response1.GetMappedAddressAttribute();
var changedAddress1 = response1.GetChangedAddressAttribute();
var mappedAddress1 = response1.Message.GetMappedAddressAttribute();
var changedAddress = response1.Message.GetChangedAddressAttribute();

Status.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址

// 某些单 IP 服务器的迷惑操作
if (mappedAddress1 is null
|| changedAddress1 is null
|| Equals(changedAddress1.Address, remote1.Address)
|| changedAddress1.Port == remote1.Port)
if (mappedAddress1 is null || changedAddress is null
|| Equals(changedAddress.Address, response1.Remote.Address)
|| changedAddress.Port == response1.Remote.Port)
{
Status.NatType = NatType.UnsupportedServer;
return;
}

Status.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址

var test2 = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
};

// test II
var (response2, remote2, _) = await TestAsync(test2, RemoteEndPoint, changedAddress1, cts.Token);
var mappedAddress2 = response2.GetMappedAddressAttribute();
var response2 = await Test2Async(changedAddress, cancellationToken);
var mappedAddress2 = response2?.Message.GetMappedAddressAttribute();

if (Equals(mappedAddress1.Address, local1) && 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)
if (response2?.Message is null)
{
Status.NatType = NatType.SymmetricUdpFirewall;
Status.PublicEndPoint = mappedAddress1;
Expand All @@ -119,19 +117,18 @@ public async Task Query3489Async()
}

// NAT
if (response2 is not null && remote2 is not null)
if (response2?.Message is not null && response2.Remote is not null)
{
// 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
var type = Equals(remote1.Address, remote2.Address) || remote1.Port == remote2.Port ? NatType.UnsupportedServer : NatType.FullCone;
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;
}

// Test I(#2)
var test12 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
var (response12, _, _) = await TestAsync(test12, changedAddress1, changedAddress1, cts.Token);
var mappedAddress12 = response12.GetMappedAddressAttribute();
var response12 = await Test1_2Async(changedAddress, cancellationToken);
var mappedAddress12 = response12?.Message.GetMappedAddressAttribute();

if (mappedAddress12 is null)
{
Expand All @@ -147,70 +144,109 @@ public async Task Query3489Async()
}

// Test III
var test3 = new StunMessage5389
var response3 = await Test3Async(cancellationToken);
var mappedAddress3 = response3?.Message.GetMappedAddressAttribute();
if (mappedAddress3 is not null && response3?.Remote is not null)
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
};
var (response3, _, _) = await TestAsync(test3, changedAddress1, changedAddress1, cts.Token);
var mappedAddress3 = response3.GetMappedAddressAttribute();
if (mappedAddress3 is not null)
{
Status.NatType = NatType.RestrictedCone;
Status.PublicEndPoint = mappedAddress3;
return;
if (Equals(response3.Remote.Address, response1.Remote.Address) && response3.Remote.Port != response1.Remote.Port)
{
Status.NatType = NatType.RestrictedCone;
Status.PublicEndPoint = mappedAddress3;
return;
}
else
{
Status.NatType = NatType.UnsupportedServer;
Status.PublicEndPoint = mappedAddress3;
return;
}
}

Status.NatType = NatType.PortRestrictedCone;
Status.PublicEndPoint = mappedAddress12;
}
finally
{
await Proxy.DisconnectAsync();
await DisconnectAsync();
}
}

protected async Task<(StunMessage5389?, IPEndPoint?, IPAddress?)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken token)
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);
//var t = DateTime.Now;

// Simple retransmissions
//https://tools.ietf.org/html/rfc3489#section-9.3
//while (t + TimeSpan.FromSeconds(3) > DateTime.Now)
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(Timeout);
var (receiveBuffer, ipe, local) = await _proxy.ReceiveAsync(sendBuffer[..length], remote, receive, cts.Token);

var message = new StunMessage5389();
if (message.TryParse(receiveBuffer) && 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)
var response = new StunResponse
{
Debug.WriteLine(ex);
}
Message = message,
Remote = ipe,
LocalAddress = local
};
return response;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
return (null, null, null);
return default;
}

public virtual async ValueTask<StunResponse?> Test1Async(CancellationToken cancellationToken)
{
var message = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0
};
return await RequestAsync(message, RemoteEndPoint, RemoteEndPoint, cancellationToken);
}

public virtual async ValueTask<StunResponse?> Test2Async(IPEndPoint other, CancellationToken cancellationToken)
{
var message = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
};
return await RequestAsync(message, RemoteEndPoint, other, cancellationToken);
}

public virtual async ValueTask<StunResponse?> Test1_2Async(IPEndPoint other, CancellationToken cancellationToken)
{
var message = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0
};
return await RequestAsync(message, other, other, cancellationToken);
}

public virtual async ValueTask<StunResponse?> Test3Async(CancellationToken cancellationToken)
{
var message = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
MagicCookie = 0,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
};
return await RequestAsync(message, RemoteEndPoint, RemoteEndPoint, cancellationToken);
}

public virtual void Dispose()
public void Dispose()
{
Proxy.Dispose();
_proxy.Dispose();
}
}
}
Loading

0 comments on commit 73fed64

Please sign in to comment.