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(); } }); } } }