using STUN.Enums;
using STUN.Interfaces;
using STUN.Message;
using STUN.Proxy;
using STUN.StunResult;
using STUN.Utils;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
namespace STUN.Client
{
///
/// https://tools.ietf.org/html/rfc5389#section-7.2.1
/// https://tools.ietf.org/html/rfc5780#section-4.2
///
public class StunClient5389UDP : StunClient3489
{
#region Subject
private readonly Subject _bindingSubj = new Subject();
public IObservable BindingTestResultChanged => _bindingSubj.AsObservable();
private readonly Subject _mappingBehaviorSubj = new Subject();
public IObservable MappingBehaviorChanged => _mappingBehaviorSubj.AsObservable();
private readonly Subject _filteringBehaviorSubj = new Subject();
public IObservable FilteringBehaviorChanged => _filteringBehaviorSubj.AsObservable();
#endregion
public StunClient5389UDP(string server, ushort port = 3478, IPEndPoint local = null, IUdpProxy proxy = null, IDnsQuery dnsQuery = null)
: base(server, port, local, proxy, dnsQuery)
{
Timeout = TimeSpan.FromSeconds(3);
}
public async Task QueryAsync()
{
await Proxy.ConnectAsync();
var result = new StunResult5389();
_bindingSubj.OnNext(result.BindingTestResult);
_mappingBehaviorSubj.OnNext(result.MappingBehavior);
_filteringBehaviorSubj.OnNext(result.FilteringBehavior);
PubSubj.OnNext(result.PublicEndPoint);
result = await FilteringBehaviorTestAsync();
if (result.BindingTestResult != BindingTestResult.Success
|| result.FilteringBehavior == FilteringBehavior.UnsupportedServer
)
{
return result;
}
try
{
if (Equals(result.PublicEndPoint, result.LocalEndPoint))
{
result.MappingBehavior = MappingBehavior.Direct;
return result;
}
// MappingBehaviorTest test II
var result2 = await BindingTestBaseAsync(new IPEndPoint(result.OtherEndPoint.Address, RemoteEndPoint.Port), false);
if (result2.BindingTestResult != BindingTestResult.Success)
{
result.MappingBehavior = MappingBehavior.Fail;
return result;
}
if (Equals(result2.PublicEndPoint, result.PublicEndPoint))
{
result.MappingBehavior = MappingBehavior.EndpointIndependent;
return result;
}
// MappingBehaviorTest test III
var result3 = await BindingTestBaseAsync(result.OtherEndPoint, false);
if (result3.BindingTestResult != BindingTestResult.Success)
{
result.MappingBehavior = MappingBehavior.Fail;
return result;
}
result.MappingBehavior = Equals(result3.PublicEndPoint, result2.PublicEndPoint) ? MappingBehavior.AddressDependent : MappingBehavior.AddressAndPortDependent;
return result;
}
finally
{
_mappingBehaviorSubj.OnNext(result.MappingBehavior);
await Proxy.DisconnectAsync();
}
}
public async Task BindingTestAsync()
{
var result = await BindingTestBaseAsync(RemoteEndPoint, true);
return result;
}
private async Task BindingTestBaseAsync(IPEndPoint remote, bool notifyChanged)
{
BindingTestResult res;
var test = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest };
var (response1, _, local1) = await TestAsync(test, remote, remote);
var mappedAddress1 = AttributeExtensions.GetXorMappedAddressAttribute(response1);
var otherAddress = AttributeExtensions.GetOtherAddressAttribute(response1);
var local = local1 == null ? null : new IPEndPoint(local1, LocalEndPoint.Port);
if (response1 == null)
{
res = BindingTestResult.Fail;
}
else if (mappedAddress1 == null)
{
res = BindingTestResult.UnsupportedServer;
}
else
{
res = BindingTestResult.Success;
}
if (notifyChanged)
{
_bindingSubj.OnNext(res);
PubSubj.OnNext(mappedAddress1);
}
LocalSubj.OnNext(LocalEndPoint);
return new StunResult5389
{
BindingTestResult = res,
LocalEndPoint = local,
PublicEndPoint = mappedAddress1,
OtherEndPoint = otherAddress
};
}
public async Task MappingBehaviorTestAsync()
{
// test I
var result1 = await BindingTestBaseAsync(RemoteEndPoint, true);
try
{
if (result1.BindingTestResult != BindingTestResult.Success)
{
return result1;
}
if (result1.OtherEndPoint == null
|| Equals(result1.OtherEndPoint.Address, RemoteEndPoint.Address)
|| result1.OtherEndPoint.Port == RemoteEndPoint.Port)
{
result1.MappingBehavior = MappingBehavior.UnsupportedServer;
return result1;
}
if (Equals(result1.PublicEndPoint, result1.LocalEndPoint))
{
result1.MappingBehavior = MappingBehavior.Direct;
return result1;
}
// test II
var result2 = await BindingTestBaseAsync(new IPEndPoint(result1.OtherEndPoint.Address, RemoteEndPoint.Port), false);
if (result2.BindingTestResult != BindingTestResult.Success)
{
result1.MappingBehavior = MappingBehavior.Fail;
return result1;
}
if (Equals(result2.PublicEndPoint, result1.PublicEndPoint))
{
result1.MappingBehavior = MappingBehavior.EndpointIndependent;
return result1;
}
// test III
var result3 = await BindingTestBaseAsync(result1.OtherEndPoint, false);
if (result3.BindingTestResult != BindingTestResult.Success)
{
result1.MappingBehavior = MappingBehavior.Fail;
return result1;
}
result1.MappingBehavior = Equals(result3.PublicEndPoint, result2.PublicEndPoint) ? MappingBehavior.AddressDependent : MappingBehavior.AddressAndPortDependent;
return result1;
}
finally
{
_mappingBehaviorSubj.OnNext(result1.MappingBehavior);
}
}
public async Task FilteringBehaviorTestAsync()
{
// test I
var result1 = await BindingTestBaseAsync(RemoteEndPoint, true);
try
{
if (result1.BindingTestResult != BindingTestResult.Success)
{
return result1;
}
if (result1.OtherEndPoint == null
|| Equals(result1.OtherEndPoint.Address, RemoteEndPoint.Address)
|| result1.OtherEndPoint.Port == RemoteEndPoint.Port)
{
result1.FilteringBehavior = FilteringBehavior.UnsupportedServer;
return result1;
}
// test II
var test2 = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
};
var (response2, _, _) = await TestAsync(test2, RemoteEndPoint, result1.OtherEndPoint);
if (response2 != null)
{
result1.FilteringBehavior = FilteringBehavior.EndpointIndependent;
return result1;
}
// test III
var test3 = new StunMessage5389
{
StunMessageType = StunMessageType.BindingRequest,
Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
};
var (response3, remote3, _) = await TestAsync(test3, RemoteEndPoint, RemoteEndPoint);
if (response3 == null)
{
result1.FilteringBehavior = FilteringBehavior.AddressAndPortDependent;
return result1;
}
if (Equals(remote3.Address, RemoteEndPoint.Address) && remote3.Port != RemoteEndPoint.Port)
{
result1.FilteringBehavior = FilteringBehavior.AddressAndPortDependent;
}
else
{
result1.FilteringBehavior = FilteringBehavior.UnsupportedServer;
}
return result1;
}
finally
{
_filteringBehaviorSubj.OnNext(result1.FilteringBehavior);
}
}
public override void Dispose()
{
base.Dispose();
_bindingSubj.OnCompleted();
_mappingBehaviorSubj.OnCompleted();
_filteringBehaviorSubj.OnCompleted();
}
}
}