using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Open.Nat
{
///
///
///
public class NatDiscoverer
{
///
/// The TraceSource instance
/// used for debugging and Troubleshooting
///
///
/// NatUtility.TraceSource.Switch.Level = SourceLevels.Verbose;
/// NatUtility.TraceSource.Listeners.Add(new ConsoleListener());
///
///
/// At least one trace listener has to be added to the Listeners collection if a trace is required; if no listener is added
/// there will no be tracing to analyse.
///
///
/// Open.NAT only supports SourceLevels.Verbose, SourceLevels.Error, SourceLevels.Warning and SourceLevels.Information.
///
public readonly static TraceSource TraceSource = new TraceSource("Open.NAT");
private static readonly Dictionary Devices = new Dictionary();
// Finalizer is never used however its destructor, that releases the open ports, is invoked by the
// process as part of the shuting down step. So, don't remove it!
private static readonly Finalizer Finalizer = new Finalizer();
internal static readonly Timer RenewTimer = new Timer(RenewMappings, null, 5000, 2000);
///
/// Discovers and returns an UPnp or Pmp NAT device; otherwise a NatDeviceNotFoundException
/// exception is thrown after 3 seconds.
///
/// A NAT device
/// when no NAT found before 3 seconds.
public async Task DiscoverDeviceAsync()
{
var cts = new CancellationTokenSource(3 * 1000);
return await DiscoverDeviceAsync(PortMapper.Pmp | PortMapper.Upnp, cts);
}
///
/// Discovers and returns a NAT device for the specified type; otherwise a NatDeviceNotFoundException
/// exception is thrown when it is cancelled.
///
///
/// It allows to specify the NAT type to discover as well as the cancellation token in order.
///
/// Port mapper protocol; Upnp, Pmp or both
/// Cancellation token source for cancelling the discovery process
/// A NAT device
/// when no NAT found before cancellation
public async Task DiscoverDeviceAsync(PortMapper portMapper, CancellationTokenSource cancellationTokenSource)
{
Guard.IsTrue(portMapper == PortMapper.Upnp || portMapper == PortMapper.Pmp, "poertMapper");
Guard.IsNotNull(cancellationTokenSource, "cancellationTokenSource");
var devices = await DiscoverAsync(portMapper, true, cancellationTokenSource);
var device = devices.FirstOrDefault();
if(device==null)
{
throw new NatDeviceNotFoundException();
}
return device;
}
///
/// Discovers and returns all NAT devices for the specified type. If no NAT device is found it returns an empty enumerable
///
/// Port mapper protocol; Upnp, Pmp or both
/// Cancellation token source for cancelling the discovery process
/// All found NAT devices
public async Task> DiscoverDevicesAsync(PortMapper portMapper, CancellationTokenSource cancellationTokenSource)
{
Guard.IsTrue(portMapper == PortMapper.Upnp || portMapper == PortMapper.Pmp, "poertMapper");
Guard.IsNotNull(cancellationTokenSource, "cancellationTokenSource");
var devices = await DiscoverAsync(portMapper, false, cancellationTokenSource);
return devices.ToArray();
}
private async Task> DiscoverAsync(PortMapper portMapper, bool onlyOne, CancellationTokenSource cts)
{
TraceSource.LogInfo("Start Discovery");
var searcherTasks = new List>>();
if(portMapper.HasFlag(PortMapper.Upnp))
{
var upnpSearcher = new UpnpSearcher(new IPAddressesProvider());
upnpSearcher.DeviceFound += (sender, args) => { if (onlyOne) cts.Cancel(); };
searcherTasks.Add(upnpSearcher.Search(cts.Token));
}
if(portMapper.HasFlag(PortMapper.Pmp))
{
var pmpSearcher = new PmpSearcher(new IPAddressesProvider());
pmpSearcher.DeviceFound += (sender, args) => { if (onlyOne) cts.Cancel(); };
searcherTasks.Add(pmpSearcher.Search(cts.Token));
}
await Task.WhenAll(searcherTasks);
TraceSource.LogInfo("Stop Discovery");
var devices = searcherTasks.SelectMany(x => x.Result);
foreach (var device in devices)
{
var key = device.ToString();
NatDevice nat;
if(Devices.TryGetValue(key, out nat))
{
nat.Touch();
}
else
{
Devices.Add(key, device);
}
}
return devices;
}
///
/// Release all ports opened by Open.NAT.
///
///
/// If ReleaseOnShutdown value is true, it release all the mappings created through the library.
///
public static void ReleaseAll()
{
foreach (var device in Devices.Values)
{
device.ReleaseAll();
}
}
internal static void ReleaseSessionMappings()
{
foreach (var device in Devices.Values)
{
device.ReleaseSessionMappings();
}
}
private static void RenewMappings(object state)
{
Task.Factory.StartNew(async ()=>
{
foreach (var device in Devices.Values)
{
await device.RenewMappings();
}
});
}
}
}