瀏覽代碼

rework upnp discovery

Luke Pulverenti 8 年之前
父節點
當前提交
62d9eb1ec7
共有 65 個文件被更改,包括 4329 次插入700 次删除
  1. 12 2
      MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
  2. 0 1
      MediaBrowser.Controller/Dlna/ISsdpHandler.cs
  3. 2 0
      MediaBrowser.Controller/LiveTv/ProgramInfo.cs
  4. 57 29
      MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
  5. 10 2
      MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
  6. 8 5
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  7. 17 9
      MediaBrowser.Dlna/PlayTo/PlayToManager.cs
  8. 60 153
      MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
  9. 0 383
      MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
  10. 0 3
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  11. 1 4
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  12. 0 10
      MediaBrowser.Model/Configuration/AutoOnOff.cs
  13. 0 3
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  14. 0 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  15. 43 23
      MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  16. 2 18
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  17. 56 30
      MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  18. 6 3
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
  19. 7 4
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
  20. 4 3
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  21. 0 1
      MediaBrowser.Server.Implementations/packages.config
  22. 3 2
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  23. 7 7
      MediaBrowser.Server.Mono/app.config
  24. 1 3
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  25. 2 1
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  26. 32 0
      MediaBrowser.sln
  27. 97 0
      Mono.Nat/AbstractNatDevice.cs
  28. 71 0
      Mono.Nat/AsyncResults/AsyncResult.cs
  29. 36 0
      Mono.Nat/Enums/MapState.cs
  30. 36 0
      Mono.Nat/Enums/ProtocolType.cs
  31. 45 0
      Mono.Nat/EventArgs/DeviceEventArgs.cs
  32. 87 0
      Mono.Nat/Exceptions/MappingException.cs
  33. 50 0
      Mono.Nat/IMapper.cs
  34. 62 0
      Mono.Nat/INatDevice.cs
  35. 51 0
      Mono.Nat/ISearcher.cs
  36. 123 0
      Mono.Nat/Mapping.cs
  37. 104 0
      Mono.Nat/Mono.Nat.csproj
  38. 9 0
      Mono.Nat/NatProtocol.cs
  39. 264 0
      Mono.Nat/NatUtility.cs
  40. 52 0
      Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
  41. 83 0
      Mono.Nat/Pmp/Mappers/PmpMapper.cs
  42. 118 0
      Mono.Nat/Pmp/Pmp.cs
  43. 56 0
      Mono.Nat/Pmp/PmpConstants.cs
  44. 347 0
      Mono.Nat/Pmp/PmpNatDevice.cs
  45. 149 0
      Mono.Nat/Pmp/Searchers/PmpSearcher.cs
  46. 31 0
      Mono.Nat/Properties/AssemblyInfo.cs
  47. 56 0
      Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
  48. 75 0
      Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
  49. 110 0
      Mono.Nat/Upnp/Mappers/UpnpMapper.cs
  50. 60 0
      Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
  51. 63 0
      Mono.Nat/Upnp/Messages/ErrorMessage.cs
  52. 62 0
      Mono.Nat/Upnp/Messages/GetServicesMessage.cs
  53. 75 0
      Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
  54. 57 0
      Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
  55. 51 0
      Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
  56. 55 0
      Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
  57. 60 0
      Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
  58. 46 0
      Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
  59. 44 0
      Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
  60. 53 0
      Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
  61. 108 0
      Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
  62. 132 0
      Mono.Nat/Upnp/Messages/UpnpMessage.cs
  63. 287 0
      Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
  64. 83 0
      Mono.Nat/Upnp/Upnp.cs
  65. 651 0
      Mono.Nat/Upnp/UpnpNatDevice.cs

+ 12 - 2
MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs

@@ -1,10 +1,20 @@
 using System;
+using System.Collections.Generic;
+using System.Net;
+using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Controller.Dlna
 {
     public interface IDeviceDiscovery
     {
-        event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
-        event EventHandler<SsdpMessageEventArgs> DeviceLeft;
+        event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+        event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
+    }
+
+    public class UpnpDeviceInfo
+    {
+        public Uri Location { get; set; }
+        public Dictionary<string, string> Headers { get; set; }
+        public IPEndPoint LocalEndPoint { get; set; }
     }
 }

+ 0 - 1
MediaBrowser.Controller/Dlna/ISsdpHandler.cs

@@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna
 {
     public interface ISsdpHandler
     {
-        event EventHandler<SsdpMessageEventArgs> MessageReceived;
     }
 }

+ 2 - 0
MediaBrowser.Controller/LiveTv/ProgramInfo.cs

@@ -107,6 +107,8 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value>The image URL.</value>
         public string ImageUrl { get; set; }
 
+        public string LogoImageUrl { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether this instance has image.
         /// </summary>

+ 57 - 29
MediaBrowser.Dlna/Main/DlnaEntryPoint.cs

@@ -14,8 +14,10 @@ using MediaBrowser.Dlna.Ssdp;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.MediaEncoding;
+using Rssdp;
 
 namespace MediaBrowser.Dlna.Main
 {
@@ -38,12 +40,11 @@ namespace MediaBrowser.Dlna.Main
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaEncoder _mediaEncoder;
 
-        private readonly SsdpHandler _ssdpHandler;
         private readonly IDeviceDiscovery _deviceDiscovery;
 
-        private readonly List<string> _registeredServerIds = new List<string>();
         private bool _ssdpHandlerStarted;
         private bool _dlnaServerStarted;
+        private SsdpDevicePublisher _Publisher;
 
         public DlnaEntryPoint(IServerConfigurationManager config,
             ILogManager logManager,
@@ -58,7 +59,7 @@ namespace MediaBrowser.Dlna.Main
             IUserDataManager userDataManager,
             ILocalizationManager localization,
             IMediaSourceManager mediaSourceManager,
-            ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
+            IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
         {
             _config = config;
             _appHost = appHost;
@@ -74,7 +75,6 @@ namespace MediaBrowser.Dlna.Main
             _mediaSourceManager = mediaSourceManager;
             _deviceDiscovery = deviceDiscovery;
             _mediaEncoder = mediaEncoder;
-            _ssdpHandler = (SsdpHandler)ssdpHandler;
             _logger = logManager.GetLogger("Dlna");
         }
 
@@ -154,7 +154,7 @@ namespace MediaBrowser.Dlna.Main
         {
             try
             {
-                _ssdpHandler.Start();
+                StartPublishing();
                 _ssdpHandlerStarted = true;
 
                 StartDeviceDiscovery();
@@ -165,13 +165,16 @@ namespace MediaBrowser.Dlna.Main
             }
         }
 
+        private void StartPublishing()
+        {
+            _Publisher = new SsdpDevicePublisher();
+        }
+
         private void StartDeviceDiscovery()
         {
             try
             {
-                ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler);
-
-                //DlnaChannel.Current.Start(() => _registeredServerIds.ToList());
+                ((DeviceDiscovery)_deviceDiscovery).Start();
             }
             catch (Exception ex)
             {
@@ -199,8 +202,6 @@ namespace MediaBrowser.Dlna.Main
             {
                 ((DeviceDiscovery)_deviceDiscovery).Dispose();
 
-                _ssdpHandler.Dispose();
-
                 _ssdpHandlerStarted = false;
             }
             catch (Exception ex)
@@ -225,6 +226,14 @@ namespace MediaBrowser.Dlna.Main
 
         private async Task RegisterServerEndpoints()
         {
+            if (!_config.GetDlnaConfiguration().BlastAliveMessages)
+            {
+                return;
+            }
+
+            var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds*2;
+            _Publisher.SupportPnpRootDevice = true;
+
             foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false))
             {
                 //if (IPAddress.IsLoopback(address))
@@ -234,25 +243,41 @@ namespace MediaBrowser.Dlna.Main
                 //}
 
                 var addressString = address.ToString();
-                var udn = addressString.GetMD5().ToString("N");
-
-                var descriptorURI = "/dlna/" + udn + "/description.xml";
-
-                var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
 
                 var services = new List<string>
                 {
-                    "upnp:rootdevice",
                     "urn:schemas-upnp-org:device:MediaServer:1",
                     "urn:schemas-upnp-org:service:ContentDirectory:1",
                     "urn:schemas-upnp-org:service:ConnectionManager:1",
-                    "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
-                    "uuid:" + udn
+                    "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
                 };
 
-                _ssdpHandler.RegisterNotification(udn, uri, address, services);
+                var udn = (addressString).GetMD5().ToString("N");
+
+                foreach (var fullService in services)
+                {
+                    var descriptorURI = "/dlna/" + udn + "/description.xml";
+                    var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
+
+                    var service = fullService.Replace("urn:", string.Empty).Replace(":1", string.Empty);
 
-                _registeredServerIds.Add(udn);
+                    var serviceParts = service.Split(':');
+
+                    var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
+
+                    _Publisher.AddDevice(new SsdpRootDevice
+                    {
+                        CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info.
+                        Location = uri, // Must point to the URL that serves your devices UPnP description document. 
+                        DeviceTypeNamespace = deviceTypeNamespace,
+                        DeviceClass = serviceParts[1],
+                        DeviceType = serviceParts[2],
+                        FriendlyName = "Emby Server",
+                        Manufacturer = "Emby",
+                        ModelName = "Emby Server",
+                        Uuid = udn // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.                
+                    });
+                }
             }
         }
 
@@ -315,20 +340,23 @@ namespace MediaBrowser.Dlna.Main
 
         public void DisposeDlnaServer()
         {
-            foreach (var id in _registeredServerIds)
+            if (_Publisher != null)
             {
-                try
-                {
-                    _ssdpHandler.UnregisterNotification(id);
-                }
-                catch (Exception ex)
+                var devices = _Publisher.Devices.ToList();
+                foreach (var device in devices)
                 {
-                    _logger.ErrorException("Error unregistering server", ex);
+                    try
+                    {
+                        _Publisher.RemoveDevice(device);
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error sending bye bye", ex);
+                    }
                 }
+                _Publisher.Dispose();
             }
 
-            _registeredServerIds.Clear();
-
             _dlnaServerStarted = false;
         }
     }

+ 10 - 2
MediaBrowser.Dlna/MediaBrowser.Dlna.csproj

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -14,6 +14,7 @@
     <SchemaVersion>2.0</SchemaVersion>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -23,7 +24,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>none</DebugType>
@@ -50,8 +51,15 @@
     <Reference Include="Patterns.Logging">
       <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
     </Reference>
+    <Reference Include="Rssdp.NetFx40">
+      <HintPath>..\ThirdParty\rssdp\Rssdp.NetFx40.dll</HintPath>
+    </Reference>
+    <Reference Include="Rssdp.Portable">
+      <HintPath>..\ThirdParty\rssdp\Rssdp.Portable.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Net.Http" />
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="Microsoft.CSharp" />

+ 8 - 5
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -19,6 +19,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Dlna.PlayTo
 {
@@ -122,16 +123,18 @@ namespace MediaBrowser.Dlna.PlayTo
             }
         }
 
-        void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
+        void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
         {
+            var info = e.Argument;
+
             string nts;
-            e.Headers.TryGetValue("NTS", out nts);
+            info.Headers.TryGetValue("NTS", out nts);
 
             string usn;
-            if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
+            if (!info.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
 
             string nt;
-            if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
+            if (!info.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
 
             if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
                 !_disposed)
@@ -653,7 +656,7 @@ namespace MediaBrowser.Dlna.PlayTo
                 _device.PlaybackProgress -= _device_PlaybackProgress;
                 _device.PlaybackStopped -= _device_PlaybackStopped;
                 _device.MediaChanged -= _device_MediaChanged;
-                _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
+                //_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
                 _device.OnDeviceUnavailable = null;
 
                 _device.Dispose();

+ 17 - 9
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -12,7 +12,9 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
+using System.Threading.Tasks;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Dlna.PlayTo
 {
@@ -61,16 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo
             _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
         }
 
-        async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+        async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
         {
+            var info = e.Argument;
+
             string usn;
-            if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+            if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
 
             string nt;
-            if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+            if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
 
-            string location;
-            if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty;
+            string location = info.Location.ToString();
 
             // It has to report that it's a media renderer
             if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -100,7 +103,7 @@ namespace MediaBrowser.Dlna.PlayTo
                     }
                 }
 
-                var uri = new Uri(location);
+                var uri = info.Location;
                 _logger.Debug("Attempting to create PlayToController from location {0}", location);
                 var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
 
@@ -121,7 +124,7 @@ namespace MediaBrowser.Dlna.PlayTo
 
                 if (controller == null)
                 {
-                    var serverAddress = GetServerAddress(e.LocalEndPoint.Address);
+                    var serverAddress = await GetServerAddress(info.LocalEndPoint == null ? null : info.LocalEndPoint.Address).ConfigureAwait(false);
                     string accessToken = null;
 
                     sessionInfo.SessionController = controller = new PlayToController(sessionInfo,
@@ -173,9 +176,14 @@ namespace MediaBrowser.Dlna.PlayTo
             }
         }
 
-        private string GetServerAddress(IPAddress localIp)
+        private Task<string> GetServerAddress(IPAddress localIp)
         {
-            return _appHost.GetLocalApiUrl(localIp);
+            if (localIp == null)
+            {
+                return _appHost.GetLocalApiUrl();
+            }
+
+            return Task.FromResult(_appHost.GetLocalApiUrl(localIp));
         }
 
         public void Dispose()

+ 60 - 153
MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs

@@ -11,6 +11,8 @@ using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
+using Rssdp;
 
 namespace MediaBrowser.Dlna.Ssdp
 {
@@ -20,132 +22,43 @@ namespace MediaBrowser.Dlna.Ssdp
 
         private readonly ILogger _logger;
         private readonly IServerConfigurationManager _config;
-        private SsdpHandler _ssdpHandler;
         private readonly CancellationTokenSource _tokenSource;
-        private readonly IServerApplicationHost _appHost;
 
-        public event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
-        public event EventHandler<SsdpMessageEventArgs> DeviceLeft;
-        private readonly INetworkManager _networkManager;
+        public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+        public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
 
-        public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost, INetworkManager networkManager)
+        private SsdpDeviceLocator _DeviceLocator;
+
+        public DeviceDiscovery(ILogger logger, IServerConfigurationManager config)
         {
             _tokenSource = new CancellationTokenSource();
 
             _logger = logger;
             _config = config;
-            _appHost = appHost;
-            _networkManager = networkManager;
-        }
-
-		private List<IPAddress> GetLocalIpAddresses()
-		{
-		    return _networkManager.GetLocalIpAddresses().ToList();
-		}
-
-        public void Start(SsdpHandler ssdpHandler)
-        {
-            _ssdpHandler = ssdpHandler;
-            _ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived;
-
-            foreach (var localIp in GetLocalIpAddresses())
-			{
-				try
-				{
-					CreateListener(localIp);
-				}
-				catch (Exception e)
-				{
-					_logger.ErrorException("Failed to Initilize Socket", e);
-				}
-			}
         }
 
-        async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
+        // Call this method from somewhere in your code to start the search.
+        public void BeginSearch()
         {
-            string nts;
-            e.Headers.TryGetValue("NTS", out nts);
-
-            if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) &&
-                String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) &&
-                !_disposed)
-            {
-                EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger);
-                return;
-            }
-
-            try
-            {
-                if (e.LocalEndPoint == null)
-                {
-                    var ip = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i));
-                    if (ip != null)
-                    {
-                        e.LocalEndPoint = new IPEndPoint(ip, 0);
-                    }
-                }
-
-                if (e.LocalEndPoint != null)
-                {
-                    TryCreateDevice(e);
-                }
-            }
-            catch (OperationCanceledException)
-            {
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error creating play to controller", ex);
-            }
-        }
-
-        private void CreateListener(IPAddress localIp)
-        {
-            Task.Factory.StartNew(async (o) =>
-            {
-                try
-                {
-					_logger.Info("Creating SSDP listener on {0}", localIp);
-
-					var endPoint = new IPEndPoint(localIp, 1900);
-
-                    using (var socket = GetMulticastSocket(localIp, endPoint))
-                    {
-                        var receiveBuffer = new byte[64000];
-
-                        CreateNotifier(localIp);
-
-                        while (!_tokenSource.IsCancellationRequested)
-                        {
-                            var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
-
-                            if (receivedBytes > 0)
-                            {
-                                var args = SsdpHelper.ParseSsdpResponse(receiveBuffer);
-                                args.EndPoint = endPoint;
-                                args.LocalEndPoint = new IPEndPoint(localIp, 0);
-
-                                _ssdpHandler.LogMessageReceived(args, true);
-
-                                TryCreateDevice(args);
-                            }
-                        }
-                    }
-
-                    _logger.Info("SSDP listener - Task completed");
-                }
-                catch (OperationCanceledException)
-                {
-                }
-                catch (Exception e)
-                {
-                    _logger.ErrorException("Error in listener", e);
-                }
-
-            }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+            _DeviceLocator = new SsdpDeviceLocator();
+
+            // (Optional) Set the filter so we only see notifications for devices we care about 
+            // (can be any search target value i.e device type, uuid value etc - any value that appears in the 
+            // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
+            //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
+
+            // Connect our event handler so we process devices as they are found
+            _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
+            _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
+            // Enable listening for notifications (optional)
+            _DeviceLocator.StartListeningForNotifications();
+
+            // Perform a search so we don't have to wait for devices to broadcast notifications 
+            // again to get any results right away (notifications are broadcast periodically).
+            StartAsyncSearch();
         }
 
-        private void CreateNotifier(IPAddress localIp)
+        private void StartAsyncSearch()
         {
             Task.Factory.StartNew(async (o) =>
             {
@@ -153,7 +66,7 @@ namespace MediaBrowser.Dlna.Ssdp
                 {
                     while (true)
                     {
-                        _ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900));
+                        await _DeviceLocator.SearchAsync().ConfigureAwait(false);
 
                         var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
 
@@ -165,66 +78,60 @@ namespace MediaBrowser.Dlna.Ssdp
                 }
                 catch (Exception ex)
                 {
-                    _logger.ErrorException("Error in notifier", ex);
+                    _logger.ErrorException("Error searching for devices", ex);
                 }
 
-            }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+            }, CancellationToken.None, TaskCreationOptions.LongRunning);
         }
 
-        private Socket GetMulticastSocket(IPAddress localIpAddress, EndPoint localEndpoint)
+        // Process each found device in the event handler
+        void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e)
         {
-            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
-            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
-            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress));
-            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
+            var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
 
-            socket.Bind(localEndpoint);
+            var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
 
-            return socket;
-        }
-
-        private void TryCreateDevice(SsdpMessageEventArgs args)
-        {
-            string nts;
-            args.Headers.TryGetValue("NTS", out nts);
+            var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
 
-            if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
+            var args = new GenericEventArgs<UpnpDeviceInfo>
             {
-                if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase))
+                Argument = new UpnpDeviceInfo
                 {
-                    if (!_disposed)
-                    {
-                        EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
-                    }
+                    Location = e.DiscoveredDevice.DescriptionLocation,
+                    Headers = headers
                 }
+            };
 
-                return;
-            }
+            EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
+        }
 
-            string usn;
-            if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+        private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
+        {
+            var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
+
+            var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
 
-            string nt;
-            if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+            var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
 
-            // Need to be able to download device description
-            string location;
-            if (!args.Headers.TryGetValue("Location", out location) ||
-                string.IsNullOrEmpty(location))
+            var args = new GenericEventArgs<UpnpDeviceInfo>
             {
-                return;
-            }
+                Argument = new UpnpDeviceInfo
+                {
+                    Location = e.DiscoveredDevice.DescriptionLocation,
+                    Headers = headers
+                }
+            };
 
-            EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
+            EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
         }
 
-        public void Dispose()
+        public void Start()
         {
-            if (_ssdpHandler != null)
-            {
-                _ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived;
-            }
+            BeginSearch();
+        }
 
+        public void Dispose()
+        {
             if (!_disposed)
             {
                 _disposed = true;

+ 0 - 383
MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

@@ -83,90 +83,6 @@ namespace MediaBrowser.Dlna.Ssdp
             }
         }
 
-        public event EventHandler<SsdpMessageEventArgs> MessageReceived;
-
-        private async void OnMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
-        {
-            if (IgnoreMessage(args, isMulticast))
-            {
-                return;
-            }
-
-            LogMessageReceived(args, isMulticast);
-
-            var headers = args.Headers;
-            string st;
-
-            if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase) && headers.TryGetValue("st", out st))
-            {
-                TimeSpan delay = GetSearchDelay(headers);
-
-                if (_config.GetDlnaConfiguration().EnableDebugLog)
-                {
-                    _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds);
-                }
-
-                await Task.Delay(delay).ConfigureAwait(false);
-
-                RespondToSearch(args.EndPoint, st);
-            }
-
-            EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
-        }
-
-        internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
-        {
-            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
-            if (enableDebugLogging)
-            {
-                var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
-                var headerText = string.Join(",", headerTexts.ToArray());
-
-                var protocol = isMulticast ? "Multicast" : "Unicast";
-                var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
-                _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-            }
-        }
-
-        internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast)
-        {
-            string usn;
-            if (args.Headers.TryGetValue("USN", out usn))
-            {
-                // USN=uuid:b67df29b5c379445fde78c3774ab518d::urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
-                if (RegisteredDevices.Any(i => string.Equals(i.USN, usn, StringComparison.OrdinalIgnoreCase)))
-                {
-                    //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
-                    //var headerText = string.Join(",", headerTexts.ToArray());
-
-                    //var protocol = isMulticast ? "Multicast" : "Unicast";
-                    //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
-                    //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
-                    return true;
-                }
-            }
-
-            string serverId;
-            if (args.Headers.TryGetValue("X-EMBY-SERVERID", out serverId))
-            {
-                if (string.Equals(serverId, _appHost.SystemId, StringComparison.OrdinalIgnoreCase))
-                {
-                    //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
-                    //var headerText = string.Join(",", headerTexts.ToArray());
-
-                    //var protocol = isMulticast ? "Multicast" : "Unicast";
-                    //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
-                    //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
         public IEnumerable<UpnpDevice> RegisteredDevices
         {
             get
@@ -188,8 +104,6 @@ namespace MediaBrowser.Dlna.Ssdp
             RestartSocketListener();
             ReloadAliveNotifier();
 
-            CreateUnicastClient();
-
             SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
         }
@@ -202,32 +116,6 @@ namespace MediaBrowser.Dlna.Ssdp
             }
         }
 
-        public void SendSearchMessage(EndPoint localIp)
-        {
-            var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-            values["HOST"] = "239.255.255.250:1900";
-            values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
-            values["X-EMBY-SERVERID"] = _appHost.SystemId;
-
-            values["MAN"] = "\"ssdp:discover\"";
-
-            // Search target
-            values["ST"] = "ssdp:all";
-
-            // Seconds to delay response
-            values["MX"] = "3";
-
-            var header = "M-SEARCH * HTTP/1.1";
-
-            var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
-            // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
-            SendDatagram(msg, _ssdpEndp, localIp, true);
-
-            SendUnicastRequest(msg);
-        }
-
         public async void SendDatagram(string msg,
             EndPoint endpoint,
             EndPoint localAddress,
@@ -248,75 +136,6 @@ namespace MediaBrowser.Dlna.Ssdp
             }
         }
 
-        /// <summary>
-        /// According to the spec: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf
-        /// Device responses should be delayed a random duration between 0 and this many seconds to balance 
-        /// load for the control point when it processes responses.  In my testing kodi times out after mx      
-        /// so we will generate from mx - 1
-        /// </summary>
-        /// <param name="headers">The mx headers</param>
-        /// <returns>A timepsan for the amount to delay before returning search result.</returns>
-        private TimeSpan GetSearchDelay(Dictionary<string, string> headers)
-        {
-            string mx;
-            headers.TryGetValue("mx", out mx);
-            int delaySeconds = 0;
-            if (!string.IsNullOrWhiteSpace(mx)
-                && int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds)
-                && delaySeconds > 1)
-            {
-                delaySeconds = new Random().Next(delaySeconds - 1);
-            }
-
-            return TimeSpan.FromSeconds(delaySeconds);
-        }
-
-        private void RespondToSearch(EndPoint endpoint, string deviceType)
-        {
-            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
-            var isLogged = false;
-
-            const string header = "HTTP/1.1 200 OK";
-
-            foreach (var d in RegisteredDevices)
-            {
-                if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
-                    string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
-                {
-                    if (!isLogged)
-                    {
-                        if (enableDebugLogging)
-                        {
-                            _logger.Debug("Responding to search from {0} for {1}", endpoint, deviceType);
-                        }
-                        isLogged = true;
-                    }
-
-                    var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-                    values["CACHE-CONTROL"] = "max-age = 600";
-                    values["DATE"] = DateTime.Now.ToString("R");
-                    values["EXT"] = "";
-                    values["LOCATION"] = d.Descriptor.ToString();
-                    values["SERVER"] = _serverSignature;
-                    values["ST"] = d.Type;
-                    values["USN"] = d.USN;
-
-                    var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
-                    SendDatagram(msg, endpoint, null, false, 2);
-                    SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2);
-                    //SendDatagram(header, values, endpoint, null, true);
-
-                    if (enableDebugLogging)
-                    {
-                        _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
-                    }
-                }
-            }
-        }
-
         private void RestartSocketListener()
         {
             if (_isDisposed)
@@ -329,8 +148,6 @@ namespace MediaBrowser.Dlna.Ssdp
                 _multicastSocket = CreateMulticastSocket();
 
                 _logger.Info("MultiCast socket created");
-
-                Receive();
             }
             catch (Exception ex)
             {
@@ -339,74 +156,6 @@ namespace MediaBrowser.Dlna.Ssdp
             }
         }
 
-        private void Receive()
-        {
-            try
-            {
-                var buffer = new byte[1024];
-
-                EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
-                _multicastSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
-            }
-            catch (ObjectDisposedException)
-            {
-                if (!_isDisposed)
-                {
-                    //StartSocketRetryTimer();
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.Debug("Error in BeginReceiveFrom", ex);
-            }
-        }
-
-        private void ReceiveCallback(IAsyncResult result)
-        {
-            if (_isDisposed)
-            {
-                return;
-            }
-
-            try
-            {
-                EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
-                var length = _multicastSocket.EndReceiveFrom(result, ref endpoint);
-
-                var received = (byte[])result.AsyncState;
-
-                var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
-                if (enableDebugLogging)
-                {
-                    _logger.Debug(Encoding.ASCII.GetString(received));
-                }
-
-                var args = SsdpHelper.ParseSsdpResponse(received);
-                args.EndPoint = endpoint;
-
-                OnMessageReceived(args, true);
-            }
-            catch (ObjectDisposedException)
-            {
-                if (!_isDisposed)
-                {
-                    //StartSocketRetryTimer();
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Failed to read SSDP message", ex);
-            }
-
-            if (_multicastSocket != null)
-            {
-                Receive();
-            }
-        }
-
         public void Dispose()
         {
             _config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
@@ -414,7 +163,6 @@ namespace MediaBrowser.Dlna.Ssdp
 
             _isDisposed = true;
 
-            DisposeUnicastClient();
             DisposeSocket();
             StopAliveNotifier();
         }
@@ -523,137 +271,6 @@ namespace MediaBrowser.Dlna.Ssdp
             }
         }
 
-        private void CreateUnicastClient()
-        {
-            if (_unicastClient == null)
-            {
-                try
-                {
-                    _unicastClient = new UdpClient(_unicastPort);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error creating unicast client", ex);
-                }
-
-                UnicastSetBeginReceive();
-            }
-        }
-
-        private void DisposeUnicastClient()
-        {
-            if (_unicastClient != null)
-            {
-                try
-                {
-                    _unicastClient.Close();
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error closing unicast client", ex);
-                }
-
-                _unicastClient = null;
-            }
-        }
-
-        /// <summary>
-        /// Listen for Unicast SSDP Responses
-        /// </summary>
-        private void UnicastSetBeginReceive()
-        {
-            try
-            {
-                var ipRxEnd = new IPEndPoint(IPAddress.Any, _unicastPort);
-                var udpListener = new UdpState { EndPoint = ipRxEnd };
-
-                udpListener.UdpClient = _unicastClient;
-                _unicastClient.BeginReceive(UnicastReceiveCallback, udpListener);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error in UnicastSetBeginReceive", ex);
-            }
-        }
-
-        /// <summary>
-        /// The UnicastReceiveCallback receives Http Responses 
-        /// and Fired the SatIpDeviceFound Event for adding the SatIpDevice  
-        /// </summary>
-        /// <param name="ar"></param>
-        private void UnicastReceiveCallback(IAsyncResult ar)
-        {
-            var udpClient = ((UdpState)(ar.AsyncState)).UdpClient;
-            var endpoint = ((UdpState)(ar.AsyncState)).EndPoint;
-            if (udpClient.Client != null)
-            {
-                try
-                {
-                    var responseBytes = udpClient.EndReceive(ar, ref endpoint);
-                    var args = SsdpHelper.ParseSsdpResponse(responseBytes);
-
-                    args.EndPoint = endpoint;
-
-                    OnMessageReceived(args, false);
-
-                    UnicastSetBeginReceive();
-                }
-                catch (ObjectDisposedException)
-                {
-
-                }
-                catch (SocketException)
-                {
-
-                }
-                catch (Exception)
-                {
-                    // If called while shutting down, seeing a NullReferenceException inside EndReceive
-                }
-            }
-        }
-
-        private void SendUnicastRequest(string request, int sendCount = 3)
-        {
-            if (_unicastClient == null)
-            {
-                return;
-            }
-
-            var ipSsdp = IPAddress.Parse(SSDPAddr);
-            var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
-
-            SendUnicastRequest(request, ipTxEnd, sendCount);
-        }
-
-        private async void SendUnicastRequest(string request, IPEndPoint toEndPoint, int sendCount = 3)
-        {
-            if (_unicastClient == null)
-            {
-                return;
-            }
-
-            //_logger.Debug("Sending unicast request");
-
-            byte[] req = Encoding.ASCII.GetBytes(request);
-
-            try
-            {
-                for (var i = 0; i < sendCount; i++)
-                {
-                    if (i > 0)
-                    {
-                        await Task.Delay(50).ConfigureAwait(false);
-                    }
-                    _unicastClient.Send(req, req.Length, toEndPoint);
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error in SendUnicastRequest", ex);
-            }
-        }
-
         private readonly object _notificationTimerSyncLock = new object();
         private int _aliveNotifierIntervalMs;
         private void ReloadAliveNotifier()

+ 0 - 3
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -175,9 +175,6 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
       <Link>Configuration\AccessSchedule.cs</Link>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
-      <Link>Configuration\AutoOnOff.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>

+ 1 - 4
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -147,9 +147,6 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
       <Link>Configuration\AccessSchedule.cs</Link>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
-      <Link>Configuration\AutoOnOff.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>
@@ -1193,4 +1190,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
+</Project>

+ 0 - 10
MediaBrowser.Model/Configuration/AutoOnOff.cs

@@ -1,10 +0,0 @@
-
-namespace MediaBrowser.Model.Configuration
-{
-    public enum AutoOnOff
-    {
-        Auto,
-        Enabled,
-        Disabled
-    }
-}

+ 0 - 3
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -183,8 +183,6 @@ namespace MediaBrowser.Model.Configuration
 
         public int RemoteClientBitrateLimit { get; set; }
 
-        public AutoOnOff EnableLibraryMonitor { get; set; }
-
         public int SharingExpirationDays { get; set; }
 
         public string[] Migrations { get; set; }
@@ -244,7 +242,6 @@ namespace MediaBrowser.Model.Configuration
             // 5 minutes
             MinResumeDurationSeconds = 300;
 
-            EnableLibraryMonitor = AutoOnOff.Auto;
             LibraryMonitorDelay = 60;
 
             EnableInternetProviders = true;

+ 0 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -89,7 +89,6 @@
     <Compile Include="Chapters\RemoteChapterResult.cs" />
     <Compile Include="Collections\CollectionCreationResult.cs" />
     <Compile Include="Configuration\AccessSchedule.cs" />
-    <Compile Include="Configuration\AutoOnOff.cs" />
     <Compile Include="Configuration\ChannelOptions.cs" />
     <Compile Include="Configuration\ChapterOptions.cs" />
     <Compile Include="Configuration\CinemaModeConfiguration.cs" />

+ 43 - 23
MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -9,6 +9,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Net;
 using MediaBrowser.Common.Threading;
+using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Server.Implementations.EntryPoints
 {
@@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
         private readonly IServerApplicationHost _appHost;
         private readonly ILogger _logger;
         private readonly IServerConfigurationManager _config;
-        private readonly ISsdpHandler _ssdp;
+        private readonly IDeviceDiscovery _deviceDiscovery;
 
         private PeriodicTimer _timer;
         private bool _isStarted;
 
-        public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
+        public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery)
         {
             _logger = logmanager.GetLogger("PortMapper");
             _appHost = appHost;
             _config = config;
-            _ssdp = ssdp;
+            _deviceDiscovery = deviceDiscovery;
         }
 
         private string _lastConfigIdentifier;
@@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
         public void Run()
         {
-            //NatUtility.Logger = new LogWriter(_logger);
+            NatUtility.Logger = _logger;
 
             if (_config.Configuration.EnableUPnP)
             {
@@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
             _timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
 
-            _ssdp.MessageReceived += _ssdp_MessageReceived;
+            _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
 
             _lastConfigIdentifier = GetConfigIdentifier();
 
             _isStarted = true;
         }
 
-        private void ClearCreatedRules(object state)
-        {
-            _createdRules = new List<string>();
-            _usnsHandled = new List<string>();
-        }
-
-        void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
+        private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
         {
-            var endpoint = e.EndPoint as IPEndPoint;
-
-            if (endpoint == null || e.LocalEndPoint == null)
-            {
-                return;
-            }
+            var info = e.Argument;
 
             string usn;
-            if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+            if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
 
             string nt;
-            if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+            if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
 
             // Filter device type
             if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
             var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
 
-            if (!_usnsHandled.Contains(identifier))
+            if (info.Location != null && !_usnsHandled.Contains(identifier))
             {
                 _usnsHandled.Add(identifier);
 
                 _logger.Debug("Calling Nat.Handle on " + identifier);
-                NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
+
+                IPAddress address;
+                if (IPAddress.TryParse(info.Location.Host, out address))
+                {
+                    // The Handle method doesn't need the port
+                    var endpoint = new IPEndPoint(address, info.Location.Port);
+
+                    IPAddress localAddress = null;
+
+                    try
+                    {
+                        var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
+
+                        if (!IPAddress.TryParse(localAddressString, out localAddress))
+                        {
+                            return;
+                        }
+                    }
+                    catch
+                    {
+                        return;
+                    }
+
+                    NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp);
+                }
             }
         }
 
+        private void ClearCreatedRules(object state)
+        {
+            _createdRules = new List<string>();
+            _usnsHandled = new List<string>();
+        }
+
         void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
         {
             var ex = e.ExceptionObject as Exception;
@@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
                 _timer = null;
             }
 
-            _ssdp.MessageReceived -= _ssdp_MessageReceived;
+            _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
 
             try
             {

+ 2 - 18
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -164,32 +164,16 @@ namespace MediaBrowser.Server.Implementations.IO
             Start();
         }
 
-        private bool EnableLibraryMonitor
-        {
-            get
-            {
-                switch (ConfigurationManager.Configuration.EnableLibraryMonitor)
-                {
-                    case AutoOnOff.Auto:
-                        return Environment.OSVersion.Platform == PlatformID.Win32NT;
-                    case AutoOnOff.Enabled:
-                        return true;
-                    default:
-                        return false;
-                }
-            }
-        }
-
         private bool IsLibraryMonitorEnabaled(BaseItem item)
         {
             var options = LibraryManager.GetLibraryOptions(item);
 
-            if (options != null && options.SchemaVersion >= 1)
+            if (options != null)
             {
                 return options.EnableRealtimeMonitor;
             }
 
-            return EnableLibraryMonitor;
+            return false;
         }
 
         public void Start()

+ 56 - 30
MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -166,7 +166,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
                             var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
                             if (imageIndex > -1)
                             {
-                                programDict[schedule.programID].images = GetProgramLogo(ApiUrl, images[imageIndex]);
+                                var programEntry = programDict[schedule.programID];
+
+                                var data = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
+                                data = data.OrderByDescending(GetSizeOrder).ToList();
+
+                                programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true);
+                                //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false);
+                                //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
+                                //    GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
+                                //    GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
+                                //    GetProgramImage(ApiUrl, data, "Banner-LOT", false);
+
+                                if (!string.IsNullOrWhiteSpace(programEntry.thumbImage))
+                                {
+                                    var b = true;
+                                }
+
+                                if (!string.IsNullOrWhiteSpace(programEntry.bannerImage))
+                                {
+                                    var b = true;
+                                }
                             }
                         }
 
@@ -179,6 +199,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
             return programsInfo;
         }
 
+        private int GetSizeOrder(ScheduleDirect.ImageData image)
+        {
+            if (!string.IsNullOrWhiteSpace(image.size))
+            {
+                int value;
+                if (int.TryParse(image.size, out value))
+                {
+                    return value;
+                }
+            }
+
+            return 0;
+        }
+
         private readonly object _channelCacheLock = new object();
         private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName)
         {
@@ -384,13 +418,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
                 episodeTitle = details.episodeTitle150;
             }
 
-            string imageUrl = null;
-
-            if (details.hasImageArtwork)
-            {
-                imageUrl = details.images;
-            }
-
             var showType = details.showType ?? string.Empty;
 
             var info = new ProgramInfo
@@ -406,7 +433,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
                 Audio = audioType,
                 IsRepeat = repeat,
                 IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1,
-                ImageUrl = imageUrl,
+                ImageUrl = details.primaryImage,
                 IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
                 IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1,
                 IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", StringComparison.OrdinalIgnoreCase) != -1,
@@ -485,36 +512,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
             return date;
         }
 
-        private string GetProgramLogo(string apiUrl, ScheduleDirect.ShowImages images)
+        private string GetProgramImage(string apiUrl, List<ScheduleDirect.ImageData> images, string category, bool returnDefaultImage)
         {
             string url = null;
-            if (images.data != null)
+
+            var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase));
+            if (logoIndex == -1)
             {
-                var smallImages = images.data.Where(i => i.size == "Sm").ToList();
-                if (smallImages.Any())
+                if (!returnDefaultImage)
                 {
-                    images.data = smallImages;
+                    return null;
                 }
-                var logoIndex = images.data.FindIndex(i => i.category == "Logo");
-                if (logoIndex == -1)
+                logoIndex = 0;
+            }
+            var uri = images[logoIndex].uri;
+
+            if (!string.IsNullOrWhiteSpace(uri))
+            {
+                if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
                 {
-                    logoIndex = 0;
+                    url = uri;
                 }
-                var uri = images.data[logoIndex].uri;
-
-                if (!string.IsNullOrWhiteSpace(uri))
+                else
                 {
-                    if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
-                    {
-                        url = uri;
-                    }
-                    else
-                    {
-                        url = apiUrl + "/image/" + uri;
-                    }
+                    url = apiUrl + "/image/" + uri;
                 }
-                //_logger.Debug("URL for image is : " + url);
             }
+            //_logger.Debug("URL for image is : " + url);
             return url;
         }
 
@@ -1204,7 +1228,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
                 public List<Crew> crew { get; set; }
                 public string showType { get; set; }
                 public bool hasImageArtwork { get; set; }
-                public string images { get; set; }
+                public string primaryImage { get; set; }
+                public string thumbImage { get; set; }
+                public string bannerImage { get; set; }
                 public string imageID { get; set; }
                 public string md5 { get; set; }
                 public List<string> contentAdvisory { get; set; }

+ 6 - 3
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs

@@ -10,6 +10,7 @@ using System;
 using System.Linq;
 using System.Threading;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Serialization;
 
 namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
         }
 
-        void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+        void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
         {
             string server = null;
-            if (e.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
+            var info = e.Argument;
+
+            if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
             {
                 string location;
-                if (e.Headers.TryGetValue("Location", out location))
+                if (info.Headers.TryGetValue("Location", out location))
                 {
                     //_logger.Debug("HdHomerun found at {0}", location);
 

+ 7 - 4
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs

@@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Extensions;
 using System.Xml.Linq;
+using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
 {
@@ -50,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
             _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
         }
 
-        void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+        void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
         {
+            var info = e.Argument;
+
             string st = null;
             string nt = null;
-            e.Headers.TryGetValue("ST", out st);
-            e.Headers.TryGetValue("NT", out nt);
+            info.Headers.TryGetValue("ST", out st);
+            info.Headers.TryGetValue("NT", out nt);
 
             if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
                 string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
             {
                 string location;
-                if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
+                if (info.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
                 {
                     _logger.Debug("SAT IP found at {0}", location);
 

+ 4 - 3
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -105,9 +105,6 @@
     <Reference Include="UniversalDetector">
       <HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
     </Reference>
-    <Reference Include="Mono.Nat">
-      <HintPath>..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll</HintPath>
-    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
@@ -390,6 +387,10 @@
       <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
+      <Project>{d7453b88-2266-4805-b39b-2b5a2a33e1ba}</Project>
+      <Name>Mono.Nat</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Localization\Ratings\us.txt" />

+ 0 - 1
MediaBrowser.Server.Implementations/packages.config

@@ -5,7 +5,6 @@
   <package id="ini-parser" version="2.3.0" targetFramework="net45" />
   <package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
   <package id="MediaBrowser.Naming" version="1.0.0.55" targetFramework="net45" />
-  <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
   <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />

+ 3 - 2
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
@@ -10,8 +10,9 @@
     <RootNamespace>MediaBrowser.Server.Mono</RootNamespace>
     <AssemblyName>MediaBrowser.Server.Mono</AssemblyName>
     <StartupObject>MediaBrowser.Server.Mono.MainClass</StartupObject>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
     <DebugSymbols>true</DebugSymbols>

+ 7 - 7
MediaBrowser.Server.Mono/app.config

@@ -1,21 +1,21 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <configSections>
-    <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
+    <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
   </configSections>
   <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     <targets async="true"></targets>
   </nlog>
   <appSettings>
-    <add key="DebugProgramDataPath" value="ProgramData-Server" />
-    <add key="ReleaseProgramDataPath" value="ProgramData-Server" />
+    <add key="DebugProgramDataPath" value="ProgramData-Server"/>
+    <add key="ReleaseProgramDataPath" value="ProgramData-Server"/>
   </appSettings>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
-        <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
+        <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
+        <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
       </dependentAssembly>
     </assemblyBinding>
   </runtime>
-</configuration>
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>

+ 1 - 3
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -549,7 +549,7 @@ namespace MediaBrowser.Server.Startup.Common
             SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
             RegisterSingleInstance(SubtitleManager);
 
-            RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, this, NetworkManager));
+            RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager));
 
             ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
             RegisterSingleInstance(ChapterManager);
@@ -566,8 +566,6 @@ namespace MediaBrowser.Server.Startup.Common
             await sharingRepo.Initialize().ConfigureAwait(false);
             RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
 
-            RegisterSingleInstance<ISsdpHandler>(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this));
-
             var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false);
             RegisterSingleInstance(activityLogRepo);
             RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));

+ 2 - 1
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

@@ -9,9 +9,10 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>MediaBrowser.Server.Startup.Common</RootNamespace>
     <AssemblyName>MediaBrowser.Server.Startup.Common</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>

+ 32 - 0
MediaBrowser.sln

@@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
+EndProject
 Global
 	GlobalSection(Performance) = preSolution
 		HasPerformanceSessions = true
@@ -522,6 +524,36 @@ Global
 		{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU
 		{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU
 		{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.Build.0 = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.Build.0 = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.Build.0 = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU
+		{D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 97 - 0
Mono.Nat/AbstractNatDevice.cs

@@ -0,0 +1,97 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat
+{
+	public abstract class AbstractNatDevice : INatDevice
+	{
+		private DateTime lastSeen;
+		
+		protected AbstractNatDevice ()
+		{
+
+		}
+
+		public abstract IPAddress LocalAddress { get; }
+		
+		public DateTime LastSeen
+		{
+			get { return lastSeen; }
+			set { lastSeen = value; }
+		}
+
+		public virtual void CreatePortMap (Mapping mapping)
+		{
+			IAsyncResult result = BeginCreatePortMap (mapping, null, null);
+		    EndCreatePortMap(result);
+		}
+
+		public virtual void DeletePortMap (Mapping mapping)
+		{
+			IAsyncResult result = BeginDeletePortMap (mapping, null, mapping);
+			EndDeletePortMap(result);
+		}
+
+		public virtual Mapping[] GetAllMappings ()
+		{
+			IAsyncResult result = BeginGetAllMappings (null, null);
+			return EndGetAllMappings (result);
+		}
+
+		public virtual IPAddress GetExternalIP ()
+		{
+			IAsyncResult result = BeginGetExternalIP(null, null);
+			return EndGetExternalIP(result);
+		}
+
+		public virtual Mapping GetSpecificMapping (Protocol protocol, int port)
+		{
+			IAsyncResult result = this.BeginGetSpecificMapping (protocol, port, null, null);
+			return this.EndGetSpecificMapping(result);
+		}
+
+        public abstract IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState);
+		public abstract IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+		
+		public abstract IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState);
+		public abstract IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState);
+        public abstract IAsyncResult BeginGetSpecificMapping(Protocol protocol, int externalPort, AsyncCallback callback, object asyncState);
+
+		public abstract void EndCreatePortMap (IAsyncResult result);
+		public abstract void EndDeletePortMap (IAsyncResult result);
+		
+		public abstract Mapping[] EndGetAllMappings (IAsyncResult result);
+		public abstract IPAddress EndGetExternalIP (IAsyncResult result);
+		public abstract Mapping EndGetSpecificMapping (IAsyncResult result);
+	}
+}

+ 71 - 0
Mono.Nat/AsyncResults/AsyncResult.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace Mono.Nat
+{
+    internal class AsyncResult : IAsyncResult
+    {
+        private object asyncState;
+        private AsyncCallback callback;
+        private bool completedSynchronously;
+        private bool isCompleted;
+        private Exception storedException;
+        private ManualResetEvent waitHandle;
+
+        public AsyncResult(AsyncCallback callback, object asyncState)
+        {
+            this.callback = callback;
+            this.asyncState = asyncState;
+            waitHandle = new ManualResetEvent(false);
+        }
+
+        public object AsyncState
+        {
+            get { return asyncState; }
+        }
+
+        public ManualResetEvent AsyncWaitHandle
+        {
+            get { return waitHandle; }
+        }
+
+        WaitHandle IAsyncResult.AsyncWaitHandle
+        {
+            get { return waitHandle; }
+        }
+
+        public bool CompletedSynchronously
+        {
+            get { return completedSynchronously; }
+            protected internal set { completedSynchronously = value; }
+        }
+
+        public bool IsCompleted
+        {
+            get { return isCompleted; }
+            protected internal set { isCompleted = value; }
+        }
+
+        public Exception StoredException
+        {
+            get { return storedException; }
+        }
+
+        public void Complete()
+        {
+            Complete(storedException);
+        }
+
+        public void Complete(Exception ex)
+        {
+            storedException = ex;
+            isCompleted = true;
+            waitHandle.Set();
+
+            if (callback != null)
+                callback(this);
+        }
+    }
+}

+ 36 - 0
Mono.Nat/Enums/MapState.cs

@@ -0,0 +1,36 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+	public enum MapState
+	{
+		AlreadyMapped,
+		Available
+	}
+}

+ 36 - 0
Mono.Nat/Enums/ProtocolType.cs

@@ -0,0 +1,36 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+	public enum Protocol
+	{
+		Tcp,
+		Udp
+	}
+}

+ 45 - 0
Mono.Nat/EventArgs/DeviceEventArgs.cs

@@ -0,0 +1,45 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+	public class DeviceEventArgs : EventArgs
+	{
+		private INatDevice device;
+		
+		public DeviceEventArgs(INatDevice device)
+		{
+			this.device = device;
+		}
+		
+		public INatDevice Device
+		{
+			get { return this.device; }
+		}
+	}
+}

+ 87 - 0
Mono.Nat/Exceptions/MappingException.cs

@@ -0,0 +1,87 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Security.Permissions;
+
+namespace Mono.Nat
+{
+	[Serializable]
+	public class MappingException : Exception
+	{
+		private int errorCode;
+		private string errorText;
+
+		public int ErrorCode
+		{
+			get { return this.errorCode; }
+		}
+
+		public string ErrorText
+		{
+			get { return this.errorText; }
+		}
+
+		#region Constructors
+		public MappingException()
+			: base()
+		{
+		}
+
+		public MappingException(string message)
+			: base(message)
+		{
+		}
+
+		public MappingException(int errorCode, string errorText)
+			: base (string.Format ("Error {0}: {1}", errorCode, errorText))
+		{
+			this.errorCode = errorCode;
+			this.errorText = errorText;
+		}
+
+		public MappingException(string message, Exception innerException)
+			: base(message, innerException)
+		{
+		}
+
+		protected MappingException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+			: base(info, context)
+		{
+		}
+		#endregion
+
+		[SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
+		public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+		{
+			if(info==null) throw new ArgumentNullException("info");
+
+			this.errorCode = info.GetInt32("errorCode");
+			this.errorText = info.GetString("errorText");
+			base.GetObjectData(info, context);
+		}
+	}
+}

+ 50 - 0
Mono.Nat/IMapper.cs

@@ -0,0 +1,50 @@
+//
+// Authors:
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Mono.Nat
+{
+    public enum MapperType
+    {
+        Pmp,
+        Upnp
+    }
+
+    internal interface IMapper
+    {
+        event EventHandler<DeviceEventArgs> DeviceFound;
+
+        void Map(IPAddress gatewayAddress);
+
+        void Handle(IPAddress localAddres, byte[] response);
+    }
+}

+ 62 - 0
Mono.Nat/INatDevice.cs

@@ -0,0 +1,62 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat
+{
+	public interface INatDevice
+	{
+		void CreatePortMap (Mapping mapping);
+		void DeletePortMap (Mapping mapping);
+		
+		IPAddress LocalAddress { get; }
+		Mapping[] GetAllMappings ();
+		IPAddress GetExternalIP ();
+		Mapping GetSpecificMapping (Protocol protocol, int port);
+
+		IAsyncResult BeginCreatePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+		IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+
+		IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState);
+		IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState);
+		IAsyncResult BeginGetSpecificMapping (Protocol protocol, int externalPort, AsyncCallback callback, object asyncState);
+
+		void EndCreatePortMap (IAsyncResult result);
+		void EndDeletePortMap (IAsyncResult result);
+
+		Mapping[] EndGetAllMappings (IAsyncResult result);
+		IPAddress EndGetExternalIP (IAsyncResult result);
+		Mapping EndGetSpecificMapping (IAsyncResult result);
+
+		DateTime LastSeen { get; set; }
+	}
+}

+ 51 - 0
Mono.Nat/ISearcher.cs

@@ -0,0 +1,51 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net.Sockets;
+using System.Net;
+
+namespace Mono.Nat
+{
+    public delegate void NatDeviceCallback(INatDevice device);
+
+    internal interface ISearcher
+    {
+        event EventHandler<DeviceEventArgs> DeviceFound;
+        event EventHandler<DeviceEventArgs> DeviceLost;
+
+        void Search();
+        void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint);
+        DateTime NextSearch { get; }
+        NatProtocol Protocol { get; }
+    }
+}

+ 123 - 0
Mono.Nat/Mapping.cs

@@ -0,0 +1,123 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+	public class Mapping
+	{
+        private string description;
+        private DateTime expiration;
+        private int lifetime;
+        private int privatePort;
+		private Protocol protocol;
+		private int publicPort;
+		
+
+
+		public Mapping (Protocol protocol, int privatePort, int publicPort)
+			: this (protocol, privatePort, publicPort, 0)
+		{
+		}
+		
+		public Mapping (Protocol protocol, int privatePort, int publicPort, int lifetime)
+		{
+			this.protocol = protocol;
+			this.privatePort = privatePort;
+			this.publicPort = publicPort;
+			this.lifetime = lifetime;
+
+			if (lifetime == int.MaxValue)
+				this.expiration = DateTime.MaxValue;
+			else if (lifetime == 0)
+				this.expiration = DateTime.Now;
+			else
+				this.expiration = DateTime.Now.AddSeconds (lifetime);
+		}
+
+        public string Description
+        {
+            get { return description; }
+            set { description = value; }
+        }
+		
+		public Protocol Protocol
+		{
+			get { return protocol; }
+			internal set { protocol = value; }
+		}
+
+		public int PrivatePort
+		{
+			get { return privatePort; }
+			internal set { privatePort = value; }
+		}
+		
+		public int PublicPort
+		{
+			get { return publicPort; }
+			internal set { publicPort = value; }
+		}
+		
+		public int Lifetime
+		{
+			get { return lifetime; }
+			internal set { lifetime = value; }
+		}
+		
+		public DateTime Expiration
+		{
+			get { return expiration; }
+			internal set { expiration = value; }
+		}
+		
+		public bool IsExpired ()
+		{
+			return expiration < DateTime.Now;
+		}
+
+		public override bool Equals (object obj)
+		{
+			Mapping other = obj as Mapping;
+			return other == null ? false : this.protocol == other.protocol &&
+				this.privatePort == other.privatePort && this.publicPort == other.publicPort;
+		}
+
+		public override int GetHashCode()
+		{
+			return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode();
+		}
+
+        public override string ToString( )
+        {
+            return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}", 
+                this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime );
+        }
+	}
+}

+ 104 - 0
Mono.Nat/Mono.Nat.csproj

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{D7453B88-2266-4805-B39B-2B5A2A33E1BA}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Mono.Nat</RootNamespace>
+    <AssemblyName>Mono.Nat</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="..\SharedVersion.cs">
+      <Link>Properties\SharedVersion.cs</Link>
+    </Compile>
+    <Compile Include="AbstractNatDevice.cs" />
+    <Compile Include="AsyncResults\AsyncResult.cs" />
+    <Compile Include="Enums\MapState.cs" />
+    <Compile Include="Enums\ProtocolType.cs" />
+    <Compile Include="EventArgs\DeviceEventArgs.cs" />
+    <Compile Include="Exceptions\MappingException.cs" />
+    <Compile Include="IMapper.cs" />
+    <Compile Include="INatDevice.cs" />
+    <Compile Include="ISearcher.cs" />
+    <Compile Include="Mapping.cs" />
+    <Compile Include="NatProtocol.cs" />
+    <Compile Include="NatUtility.cs" />
+    <Compile Include="Pmp\AsyncResults\PortMapAsyncResult.cs" />
+    <Compile Include="Pmp\Mappers\PmpMapper.cs" />
+    <Compile Include="Pmp\Pmp.cs" />
+    <Compile Include="Pmp\PmpConstants.cs" />
+    <Compile Include="Pmp\PmpNatDevice.cs" />
+    <Compile Include="Pmp\Searchers\PmpSearcher.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Upnp\AsyncResults\GetAllMappingsAsyncResult.cs" />
+    <Compile Include="Upnp\AsyncResults\PortMapAsyncResult.cs" />
+    <Compile Include="Upnp\Mappers\UpnpMapper.cs" />
+    <Compile Include="Upnp\Messages\DiscoverDeviceMessage.cs" />
+    <Compile Include="Upnp\Messages\ErrorMessage.cs" />
+    <Compile Include="Upnp\Messages\GetServicesMessage.cs" />
+    <Compile Include="Upnp\Messages\Requests\CreatePortMappingMessage.cs" />
+    <Compile Include="Upnp\Messages\Requests\DeletePortMappingMessage.cs" />
+    <Compile Include="Upnp\Messages\Requests\GetExternalIPAddressMessage.cs" />
+    <Compile Include="Upnp\Messages\Requests\GetGenericPortMappingEntry.cs" />
+    <Compile Include="Upnp\Messages\Requests\GetSpecificPortMappingEntryMessage.cs" />
+    <Compile Include="Upnp\Messages\Responses\CreatePortMappingResponseMessage.cs" />
+    <Compile Include="Upnp\Messages\Responses\DeletePortMappingResponseMessage.cs" />
+    <Compile Include="Upnp\Messages\Responses\GetExternalIPAddressResponseMessage.cs" />
+    <Compile Include="Upnp\Messages\Responses\GetGenericPortMappingEntryResponseMessage.cs" />
+    <Compile Include="Upnp\Messages\UpnpMessage.cs" />
+    <Compile Include="Upnp\Searchers\UpnpSearcher.cs" />
+    <Compile Include="Upnp\Upnp.cs" />
+    <Compile Include="Upnp\UpnpNatDevice.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+      <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+      <Name>MediaBrowser.Controller</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Name>MediaBrowser.Model</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 9 - 0
Mono.Nat/NatProtocol.cs

@@ -0,0 +1,9 @@
+
+namespace Mono.Nat
+{
+    public enum NatProtocol
+    {
+        Upnp = 0,
+        Pmp = 1
+    }
+}

+ 264 - 0
Mono.Nat/NatUtility.cs

@@ -0,0 +1,264 @@
+//
+// Authors:
+//   Ben Motmans <ben.motmans@gmail.com>
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Linq;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Logging;
+using Mono.Nat.Pmp.Mappers;
+using Mono.Nat.Upnp.Mappers;
+
+namespace Mono.Nat
+{
+	public static class NatUtility
+	{
+        private static ManualResetEvent searching;
+		public static event EventHandler<DeviceEventArgs> DeviceFound;
+		public static event EventHandler<DeviceEventArgs> DeviceLost;
+        
+        public static event EventHandler<UnhandledExceptionEventArgs> UnhandledException;
+
+		private static List<ISearcher> controllers;
+		private static bool verbose;
+
+        public static List<NatProtocol> EnabledProtocols { get; set; }
+
+	    public static ILogger Logger { get; set; }
+
+	    public static bool Verbose
+		{
+			get { return verbose; }
+			set { verbose = value; }
+		}
+		
+        static NatUtility()
+        {
+            EnabledProtocols = new List<NatProtocol>
+            {
+                NatProtocol.Upnp,
+                NatProtocol.Pmp
+            };
+
+            searching = new ManualResetEvent(false);
+
+            controllers = new List<ISearcher>();
+            controllers.Add(UpnpSearcher.Instance);
+            controllers.Add(PmpSearcher.Instance);
+
+            controllers.ForEach(searcher =>
+                {
+                    searcher.DeviceFound += (sender, args) =>
+                    {
+                        if (DeviceFound != null)
+                            DeviceFound(sender, args);
+                    };
+                    searcher.DeviceLost += (sender, args) =>
+                    {
+                        if (DeviceLost != null)
+                            DeviceLost(sender, args);
+                    };
+                });
+            Thread t = new Thread(SearchAndListen);
+            t.IsBackground = true;
+            t.Start();
+        }
+
+		internal static void Log(string format, params object[] args)
+		{
+			var logger = Logger;
+		    if (logger != null)
+		        logger.Debug(format, args);
+		}
+
+        private static void SearchAndListen()
+        {
+            while (true)
+            {
+                searching.WaitOne();
+
+                try
+                {
+                    var enabledProtocols = EnabledProtocols.ToList();
+
+                    if (enabledProtocols.Contains(UpnpSearcher.Instance.Protocol))
+                    {
+                        Receive(UpnpSearcher.Instance, UpnpSearcher.sockets);
+                    }
+                    if (enabledProtocols.Contains(PmpSearcher.Instance.Protocol))
+                    {
+                        Receive(PmpSearcher.Instance, PmpSearcher.sockets);
+                    }
+
+                    foreach (ISearcher s in controllers)
+                        if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol))
+                        {
+                            Log("Searching for: {0}", s.GetType().Name);
+							s.Search();
+                        }
+                }
+                catch (Exception e)
+                {
+                    if (UnhandledException != null)
+                        UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false));
+                }
+				Thread.Sleep(10);
+            }
+		}
+
+		static void Receive (ISearcher searcher, List<UdpClient> clients)
+		{
+			IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+			foreach (UdpClient client in clients)
+			{
+				if (client.Available > 0)
+				{
+				    IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address;
+					byte[] data = client.Receive(ref received);
+					searcher.Handle(localAddress, data, received);
+				}
+            }
+        }
+
+        static void Receive(IMapper mapper, List<UdpClient> clients)
+        {
+            IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+            foreach (UdpClient client in clients)
+            {
+                if (client.Available > 0)
+                {
+                    IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address;
+                    byte[] data = client.Receive(ref received);
+                    mapper.Handle(localAddress, data);
+                }
+            }
+        }
+		
+		public static void StartDiscovery ()
+		{
+            searching.Set();
+		}
+
+		public static void StopDiscovery ()
+		{
+            searching.Reset();
+		}
+
+        //This is for when you know the Gateway IP and want to skip the costly search...
+        public static void DirectMap(IPAddress gatewayAddress, MapperType type)
+        {
+            IMapper mapper;
+            switch (type)
+            {
+                case MapperType.Pmp:
+                    mapper = new PmpMapper();
+                    break;
+                case MapperType.Upnp:
+                    mapper = new UpnpMapper();
+                    mapper.DeviceFound += (sender, args) =>
+                    {
+                        if (DeviceFound != null)
+                            DeviceFound(sender, args);
+                    };
+                    mapper.Map(gatewayAddress);                    
+                    break;
+                default:
+                    throw new InvalidOperationException("Unsuported type given");
+
+            }
+            searching.Reset();
+            
+        }
+
+        //So then why is it here? -Nick
+		[Obsolete ("This method serves no purpose and shouldn't be used")]
+		public static IPAddress[] GetLocalAddresses (bool includeIPv6)
+		{
+			List<IPAddress> addresses = new List<IPAddress> ();
+
+			IPHostEntry hostInfo = Dns.GetHostEntry (Dns.GetHostName ());
+			foreach (IPAddress address in hostInfo.AddressList) {
+				if (address.AddressFamily == AddressFamily.InterNetwork ||
+					(includeIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) {
+					addresses.Add (address);
+				}
+			}
+			
+			return addresses.ToArray ();
+		}
+		
+		//checks if an IP address is a private address space as defined by RFC 1918
+		public static bool IsPrivateAddressSpace (IPAddress address)
+		{
+			byte[] ba = address.GetAddressBytes ();
+
+			switch ((int)ba[0]) {
+			case 10:
+				return true; //10.x.x.x
+			case 172:
+				return ((int)ba[1] & 16) != 0; //172.16-31.x.x
+			case 192:
+				return (int)ba[1] == 168; //192.168.x.x
+			default:
+				return false;
+			}
+		}
+
+	    public static void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint, NatProtocol protocol)
+	    {
+	        switch (protocol)
+	        {
+                case NatProtocol.Upnp:
+	                UpnpSearcher.Instance.Handle(localAddress, response, endpoint);
+	                break;
+                case NatProtocol.Pmp:
+	                PmpSearcher.Instance.Handle(localAddress, response, endpoint);
+	                break;
+	            default:
+	                throw new ArgumentException("Unexpected protocol: " + protocol);
+	        }
+        }
+
+        public static void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol)
+        {
+            switch (protocol)
+            {
+                case NatProtocol.Upnp:
+                    UpnpSearcher.Instance.Handle(localAddress, deviceInfo, endpoint);
+                    break;
+                default:
+                    throw new ArgumentException("Unexpected protocol: " + protocol);
+            }
+        }
+    }
+}

+ 52 - 0
Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs

@@ -0,0 +1,52 @@
+//
+// Authors:
+//   Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Pmp
+{
+	internal class PortMapAsyncResult : AsyncResult
+	{
+		private Mapping mapping;
+		
+		internal PortMapAsyncResult (Mapping mapping, AsyncCallback callback, object asyncState)
+			: base (callback, asyncState)
+		{
+			this.mapping = mapping;
+		}
+		
+		internal PortMapAsyncResult (Protocol protocol, int port, int lifetime, AsyncCallback callback, object asyncState)
+			: base (callback, asyncState)
+		{
+			this.mapping = new Mapping (protocol, port, port, lifetime);
+		}
+		
+		internal Mapping Mapping
+		{
+			get { return mapping; }
+		}
+	}
+}

+ 83 - 0
Mono.Nat/Pmp/Mappers/PmpMapper.cs

@@ -0,0 +1,83 @@
+//
+// Authors:
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using Mono.Nat.Pmp;
+
+namespace Mono.Nat.Pmp.Mappers
+{
+    internal class PmpMapper : Pmp, IMapper
+    {
+        public event EventHandler<DeviceEventArgs> DeviceFound;
+
+        static PmpMapper()
+        {
+            CreateSocketsAndAddGateways();
+        }
+
+        public void Map(IPAddress gatewayAddress)
+        {
+            sockets.ForEach(x => Map(x, gatewayAddress));
+        }
+
+        void Map(UdpClient client, IPAddress gatewayAddress)
+        {
+            // The nat-pmp search message. Must be sent to GatewayIP:53531
+            byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
+
+            client.Send(buffer, buffer.Length, new IPEndPoint(gatewayAddress, PmpConstants.ServerPort));
+        }
+
+        public void Handle(IPAddress localAddres, byte[] response)
+        {
+            //if (!IsSearchAddress(endpoint.Address))
+            //    return;
+            if (response.Length != 12)
+                return;
+            if (response[0] != PmpConstants.Version)
+                return;
+            if (response[1] != PmpConstants.ServerNoop)
+                return;
+            int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2));
+            if (errorcode != 0)
+                NatUtility.Log("Non zero error: {0}", errorcode);
+
+            IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] });
+            OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(localAddres, publicIp)));
+        }
+
+        private void OnDeviceFound(DeviceEventArgs args)
+        {
+            if (DeviceFound != null)
+                DeviceFound(this, args);
+        }  
+    }
+}

+ 118 - 0
Mono.Nat/Pmp/Pmp.cs

@@ -0,0 +1,118 @@
+//
+// Authors:
+//   Ben Motmans <ben.motmans@gmail.com>
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Mono.Nat.Pmp
+{
+    internal abstract class Pmp
+    {
+        public static List<UdpClient> sockets;
+        protected static Dictionary<UdpClient, List<IPEndPoint>> gatewayLists;
+
+        internal static void CreateSocketsAndAddGateways()
+        {
+            sockets = new List<UdpClient>();
+            gatewayLists = new Dictionary<UdpClient, List<IPEndPoint>>();
+
+            try
+            {
+                foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces())
+                {
+                    if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown)
+                        continue;
+                    IPInterfaceProperties properties = n.GetIPProperties();
+                    List<IPEndPoint> gatewayList = new List<IPEndPoint>();
+
+                    foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses)
+                    {
+                        if (gateway.Address.AddressFamily == AddressFamily.InterNetwork)
+                        {
+                            gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort));
+                        }
+                    }
+                    if (gatewayList.Count == 0)
+                    {
+                        /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */
+                        foreach (var gw2 in properties.DnsAddresses)
+                        {
+                            if (gw2.AddressFamily == AddressFamily.InterNetwork)
+                            {
+                                gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort));
+                            }
+                        }
+                        foreach (var unicast in properties.UnicastAddresses)
+                        {
+                            if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred
+							    && unicast.AddressPreferredLifetime != UInt32.MaxValue
+							    && */unicast.Address.AddressFamily == AddressFamily.InterNetwork)
+                            {
+                                var bytes = unicast.Address.GetAddressBytes();
+                                bytes[3] = 1;
+                                gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort));
+                            }
+                        }
+                    }
+
+                    if (gatewayList.Count > 0)
+                    {
+                        foreach (UnicastIPAddressInformation address in properties.UnicastAddresses)
+                        {
+                            if (address.Address.AddressFamily == AddressFamily.InterNetwork)
+                            {
+                                UdpClient client;
+
+                                try
+                                {
+                                    client = new UdpClient(new IPEndPoint(address.Address, 0));
+                                }
+                                catch (SocketException)
+                                {
+                                    continue; // Move on to the next address.
+                                }
+
+                                gatewayLists.Add(client, gatewayList);
+                                sockets.Add(client);
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                // NAT-PMP does not use multicast, so there isn't really a good fallback.
+            }
+        }
+    }
+}

+ 56 - 0
Mono.Nat/Pmp/PmpConstants.cs

@@ -0,0 +1,56 @@
+//
+// Authors:
+//   Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Pmp
+{
+    internal static class PmpConstants
+	{
+		public const byte Version = (byte)0;
+		
+		public const byte OperationCode = (byte)0;
+		public const byte OperationCodeUdp = (byte)1;
+		public const byte OperationCodeTcp = (byte)2;
+        public const byte ServerNoop = (byte)128;
+		
+		public const int ClientPort = 5350;
+		public const int ServerPort = 5351;
+		
+		public const int RetryDelay = 250;
+		public const int RetryAttempts = 9;
+		
+		public const int RecommendedLeaseTime = 60 * 60;
+		public const int DefaultLeaseTime = RecommendedLeaseTime;
+		
+		public const short ResultCodeSuccess = 0;
+		public const short ResultCodeUnsupportedVersion = 1;
+		public const short ResultCodeNotAuthorized = 2;
+		public const short ResultCodeNetworkFailure = 3;
+		public const short ResultCodeOutOfResources = 4;
+		public const short ResultCodeUnsupportedOperationCode = 5;
+	}
+}

+ 347 - 0
Mono.Nat/Pmp/PmpNatDevice.cs

@@ -0,0 +1,347 @@
+//
+// Authors:
+//   Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace Mono.Nat.Pmp
+{
+	internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice> 
+	{
+        private AsyncResult externalIpResult;
+        private bool pendingOp;
+		private IPAddress localAddress;
+		private IPAddress publicAddress;
+		
+		internal PmpNatDevice (IPAddress localAddress, IPAddress publicAddress)
+		{
+			this.localAddress = localAddress;
+			this.publicAddress = publicAddress;
+		}
+		
+		public override IPAddress LocalAddress
+		{
+			get { return localAddress; }
+		}
+
+		public override IPAddress GetExternalIP ()
+		{
+			return publicAddress;
+		}
+
+        public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+		{
+			PortMapAsyncResult pmar = new PortMapAsyncResult (mapping.Protocol, mapping.PublicPort, PmpConstants.DefaultLeaseTime, callback, asyncState);
+			ThreadPool.QueueUserWorkItem (delegate 
+            {
+				try 
+                {
+					CreatePortMap(pmar.Mapping, true);
+					pmar.Complete();
+				} 
+                catch (Exception e) 
+                {
+					pmar.Complete(e);
+				}
+			});
+			return pmar;
+		}
+
+		public override IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState)
+		{
+			PortMapAsyncResult pmar =  new PortMapAsyncResult (mapping, callback, asyncState);
+			ThreadPool.QueueUserWorkItem (delegate {
+				try {
+					CreatePortMap(pmar.Mapping, false);
+					pmar.Complete();
+				} catch (Exception e) {
+					pmar.Complete(e);
+				}
+			});
+			return pmar;
+		}
+
+		public override void EndCreatePortMap (IAsyncResult result)
+		{
+			PortMapAsyncResult pmar = result as PortMapAsyncResult;
+			pmar.AsyncWaitHandle.WaitOne ();
+		}
+
+		public override void EndDeletePortMap (IAsyncResult result)
+		{
+			PortMapAsyncResult pmar = result as PortMapAsyncResult;
+			pmar.AsyncWaitHandle.WaitOne ();
+		}
+		
+		public override IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState)
+		{
+			//NAT-PMP does not specify a way to get all port mappings
+			throw new NotSupportedException ();
+		}
+
+		public override IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState)
+		{
+            StartOp(ref externalIpResult, callback, asyncState);
+            AsyncResult result = externalIpResult;
+            result.Complete();
+            return result;
+		}
+
+		public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState)
+		{
+			//NAT-PMP does not specify a way to get a specific port map
+			throw new NotSupportedException ();
+		}
+		
+		public override Mapping[] EndGetAllMappings (IAsyncResult result)
+		{
+			//NAT-PMP does not specify a way to get all port mappings
+			throw new NotSupportedException ();
+		}
+
+		public override IPAddress EndGetExternalIP (IAsyncResult result)
+		{
+            EndOp(result, ref externalIpResult);
+			return publicAddress;
+		}
+
+        private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState)
+        {
+            if (pendingOp == true)
+                throw new InvalidOperationException("Can only have one simultaenous async operation");
+
+            pendingOp = true;
+            result = new AsyncResult(callback, asyncState);
+        }
+
+        private void EndOp(IAsyncResult supplied, ref AsyncResult actual)
+        {
+            if (supplied == null)
+                throw new ArgumentNullException("result");
+
+            if (supplied != actual)
+                throw new ArgumentException("Supplied IAsyncResult does not match the stored result");
+
+            if (!supplied.IsCompleted)
+                supplied.AsyncWaitHandle.WaitOne();
+
+            if (actual.StoredException != null)
+                throw actual.StoredException;
+
+            pendingOp = false;
+            actual = null;
+        }
+
+		public override Mapping EndGetSpecificMapping (IAsyncResult result)
+		{
+			//NAT-PMP does not specify a way to get a specific port map
+			throw new NotSupportedException ();
+		}
+		
+		public override bool Equals(object obj)
+		{
+			PmpNatDevice device = obj as PmpNatDevice;
+			return (device == null) ? false : this.Equals(device);
+		}
+		
+		public override int GetHashCode ()
+		{
+			return this.publicAddress.GetHashCode();
+		}
+
+		public bool Equals (PmpNatDevice other)
+		{
+			return (other == null) ? false : this.publicAddress.Equals(other.publicAddress);
+		}
+
+		private Mapping CreatePortMap (Mapping mapping, bool create)
+		{
+			List<byte> package = new List<byte> ();
+			
+			package.Add (PmpConstants.Version);
+			package.Add (mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp);
+			package.Add ((byte)0); //reserved
+			package.Add ((byte)0); //reserved
+			package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder((short)mapping.PrivatePort)));
+			package.AddRange (BitConverter.GetBytes (create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0));
+			package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder(mapping.Lifetime)));
+
+			CreatePortMapAsyncState state = new CreatePortMapAsyncState ();
+			state.Buffer = package.ToArray ();
+			state.Mapping = mapping;
+
+			ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapAsync), state);
+			WaitHandle.WaitAll (new WaitHandle[] {state.ResetEvent});
+			
+			if (!state.Success) {
+				string type = create ? "create" : "delete";
+				throw new MappingException (String.Format ("Failed to {0} portmap (protocol={1}, private port={2}", type, mapping.Protocol, mapping.PrivatePort));
+			}
+			
+			return state.Mapping;
+		}
+		
+		private void CreatePortMapAsync (object obj)
+		{
+			CreatePortMapAsyncState state = obj as CreatePortMapAsyncState;
+			
+			UdpClient udpClient = new UdpClient ();
+			CreatePortMapListenState listenState = new CreatePortMapListenState (state, udpClient);
+
+			int attempt = 0;
+			int delay = PmpConstants.RetryDelay;
+			
+			ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState);
+
+			while (attempt < PmpConstants.RetryAttempts && !listenState.Success) {
+				udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort));
+                listenState.UdpClientReady.Set();
+
+				attempt++;
+				delay *= 2;
+				Thread.Sleep (delay);
+			}
+			
+			state.Success = listenState.Success;
+			
+			udpClient.Close ();
+			state.ResetEvent.Set ();
+		}
+		
+		private void CreatePortMapListen (object obj)
+		{
+			CreatePortMapListenState state = obj as CreatePortMapListenState;
+
+            UdpClient udpClient = state.UdpClient;
+            state.UdpClientReady.WaitOne(); // Evidently UdpClient has some lazy-init Send/Receive race?
+			IPEndPoint endPoint = new IPEndPoint (localAddress, PmpConstants.ServerPort);
+			
+			while (!state.Success) 
+            {
+                byte[] data;
+                try
+                {
+                    data = udpClient.Receive(ref endPoint);
+                }
+                catch (SocketException)
+                {
+                    state.Success = false;
+                    return;
+                }
+
+                catch (ObjectDisposedException)
+                {
+                    state.Success = false;
+                    return;
+                }
+			
+				if (data.Length < 16)
+					continue;
+
+				if (data[0] != PmpConstants.Version)
+					continue;
+			
+				byte opCode = (byte)(data[1] & (byte)127);
+				
+				Protocol protocol = Protocol.Tcp;
+				if (opCode == PmpConstants.OperationCodeUdp)
+					protocol = Protocol.Udp;
+
+				short resultCode = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 2));
+				uint epoch = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 4));
+
+				int privatePort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 8));
+				int publicPort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 10));
+
+				uint lifetime = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 12));
+
+				if (publicPort < 0 || privatePort < 0 || resultCode != PmpConstants.ResultCodeSuccess)
+                {
+					state.Success = false;
+					return;
+				}
+				
+				if (lifetime == 0) 
+                {
+					//mapping was deleted
+					state.Success = true;
+					state.Mapping = null;
+					return;
+				} 
+                else 
+                {
+					//mapping was created
+					//TODO: verify that the private port+protocol are a match
+					Mapping mapping = state.Mapping;
+					mapping.PublicPort = publicPort;
+                    mapping.Protocol = protocol;
+					mapping.Expiration = DateTime.Now.AddSeconds (lifetime);
+
+					state.Success = true;
+				}
+			}
+		}
+
+
+        /// <summary>
+        /// Overridden.
+        /// </summary>
+        /// <returns></returns>
+        public override string ToString( )
+        {
+            return String.Format( "PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}",
+                this.localAddress, this.publicAddress, this.LastSeen );
+        }
+
+
+		private class CreatePortMapAsyncState
+		{
+			internal byte[] Buffer;
+			internal ManualResetEvent ResetEvent = new ManualResetEvent (false);
+			internal Mapping Mapping;
+			
+			internal bool Success;
+		}
+		
+		private class CreatePortMapListenState
+		{
+			internal volatile bool Success;
+			internal Mapping Mapping;
+            internal UdpClient UdpClient;
+            internal ManualResetEvent UdpClientReady;
+			
+			internal CreatePortMapListenState (CreatePortMapAsyncState state, UdpClient client)
+			{
+                Mapping = state.Mapping;
+                UdpClient = client; UdpClientReady = new ManualResetEvent(false);
+			}
+		}
+	}
+}

+ 149 - 0
Mono.Nat/Pmp/Searchers/PmpSearcher.cs

@@ -0,0 +1,149 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Pmp;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Linq;
+
+namespace Mono.Nat
+{
+    internal class PmpSearcher : Pmp.Pmp, ISearcher
+    {
+		static PmpSearcher instance = new PmpSearcher();
+        
+		
+		public static PmpSearcher Instance
+		{
+			get { return instance; }
+		}
+
+        private int timeout;
+        private DateTime nextSearch;
+        public event EventHandler<DeviceEventArgs> DeviceFound;
+        public event EventHandler<DeviceEventArgs> DeviceLost;
+
+        static PmpSearcher()
+        {
+            CreateSocketsAndAddGateways();
+        }
+
+        PmpSearcher()
+        {
+            timeout = 250;
+        }
+
+        public void Search()
+		{
+			foreach (UdpClient s in sockets)
+			{
+				try
+				{
+					Search(s);
+				}
+				catch
+				{
+					// Ignore any search errors
+				}
+			}
+		}
+
+		void Search (UdpClient client)
+        {
+            // Sort out the time for the next search first. The spec says the 
+            // timeout should double after each attempt. Once it reaches 64 seconds
+            // (and that attempt fails), assume no devices available
+            nextSearch = DateTime.Now.AddMilliseconds(timeout);
+            timeout *= 2;
+
+            // We've tried 9 times as per spec, try searching again in 5 minutes
+            if (timeout == 128 * 1000)
+            {
+                timeout = 250;
+                nextSearch = DateTime.Now.AddMinutes(10);
+                return;
+            }
+
+            // The nat-pmp search message. Must be sent to GatewayIP:53531
+            byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
+            foreach (IPEndPoint gatewayEndpoint in gatewayLists[client])
+                client.Send(buffer, buffer.Length, gatewayEndpoint);
+        }
+
+        bool IsSearchAddress(IPAddress address)
+        {
+            foreach (List<IPEndPoint> gatewayList in gatewayLists.Values)
+                foreach (IPEndPoint gatewayEndpoint in gatewayList)
+                    if (gatewayEndpoint.Address.Equals(address))
+                        return true;
+            return false;
+        }
+
+        public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+        {
+            if (!IsSearchAddress(endpoint.Address))
+                return;
+            if (response.Length != 12)
+                return;
+            if (response[0] != PmpConstants.Version)
+                return;
+            if (response[1] != PmpConstants.ServerNoop)
+                return;
+            int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2));
+            if (errorcode != 0)
+                NatUtility.Log("Non zero error: {0}", errorcode);
+
+            IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] });
+            nextSearch = DateTime.Now.AddMinutes(5);
+            timeout = 250;
+            OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp)));
+        }
+
+        public DateTime NextSearch
+        {
+            get { return nextSearch; }
+        }
+        private void OnDeviceFound(DeviceEventArgs args)
+        {
+            if (DeviceFound != null)
+                DeviceFound(this, args);
+        }
+
+        public NatProtocol Protocol
+        {
+            get { return NatProtocol.Pmp; }
+        }
+    }
+}

+ 31 - 0
Mono.Nat/Properties/AssemblyInfo.cs

@@ -0,0 +1,31 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Mono.Nat")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Mono.Nat")]
+[assembly: AssemblyCopyright("Copyright ©  2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d7453b88-2266-4805-b39b-2b5a2a33e1ba")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//

+ 56 - 0
Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs

@@ -0,0 +1,56 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+	internal class GetAllMappingsAsyncResult : PortMapAsyncResult
+	{
+		private List<Mapping> mappings;
+		private Mapping specificMapping;
+		
+		public GetAllMappingsAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
+			: base(request, callback, asyncState)
+		{
+			mappings = new List<Mapping>();
+		}
+
+		public List<Mapping> Mappings
+		{
+			get { return this.mappings; }
+		}
+
+		public Mapping SpecificMapping
+		{
+			get { return this.specificMapping; }
+			set { this.specificMapping = value; }
+		}
+	}
+}

+ 75 - 0
Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs

@@ -0,0 +1,75 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+using System.Net;
+using System.Threading;
+
+namespace Mono.Nat.Upnp
+{
+	internal class PortMapAsyncResult : AsyncResult
+	{
+		private WebRequest request;
+		private MessageBase savedMessage;
+		
+		protected PortMapAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
+			: base (callback, asyncState)
+		{
+			this.request = request;
+		}
+		
+		internal WebRequest Request
+		{
+			get { return this.request; }
+			set { this.request = value; }
+		}
+
+		internal MessageBase SavedMessage
+		{
+			get { return this.savedMessage; }
+			set { this.savedMessage = value; }
+		}
+
+		internal static PortMapAsyncResult Create (MessageBase message, WebRequest request, AsyncCallback storedCallback, object asyncState)
+		{
+			if (message is GetGenericPortMappingEntry)
+				return new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
+
+			if (message is GetSpecificPortMappingEntryMessage)
+			{
+				GetSpecificPortMappingEntryMessage mapMessage = (GetSpecificPortMappingEntryMessage)message;
+				GetAllMappingsAsyncResult result = new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
+				
+				result.SpecificMapping = new Mapping(mapMessage.protocol, 0, mapMessage.externalPort, 0);
+				return result;
+			}
+
+			return new PortMapAsyncResult(request, storedCallback, asyncState);
+		}
+	}
+}

+ 110 - 0
Mono.Nat/Upnp/Mappers/UpnpMapper.cs

@@ -0,0 +1,110 @@
+//
+// Authors:
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+
+namespace Mono.Nat.Upnp.Mappers
+{
+    internal class UpnpMapper : Upnp, IMapper
+    {
+
+        public event EventHandler<DeviceEventArgs> DeviceFound;
+
+        public UdpClient Client { get; set; }
+
+        public UpnpMapper()
+        {
+            //Bind to local port 1900 for ssdp responses
+            Client = new UdpClient(1900);
+        }
+
+        public void Map(IPAddress gatewayAddress)
+        {
+            //Get the httpu request payload
+            byte[] data = DiscoverDeviceMessage.EncodeUnicast(gatewayAddress);
+
+            Client.Send(data, data.Length, new IPEndPoint(gatewayAddress, 1900));
+
+            new Thread(Receive).Start(); 
+        }
+
+        public void Receive()
+        {
+            while (true)
+            {
+                IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+                if (Client.Available > 0)
+                {
+                    IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address;
+                    byte[] data = Client.Receive(ref received);
+                    Handle(localAddress, data, received);
+                }
+            }
+        }
+
+        public void Handle(IPAddress localAddres, byte[] response)
+        {
+            Handle(localAddres, response, null);
+        }
+
+        public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+        {
+            // No matter what, this method should never throw an exception. If something goes wrong
+            // we should still be in a position to handle the next reply correctly.
+            try
+            {
+                UpnpNatDevice d = base.Handle(localAddress, response, endpoint);               
+                d.GetServicesList(DeviceSetupComplete);
+            }
+            catch (Exception ex)
+            {
+                Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: ");
+                Trace.WriteLine("ErrorMessage:");
+                Trace.WriteLine(ex.Message);
+                Trace.WriteLine("Data string:");
+                Trace.WriteLine(Encoding.UTF8.GetString(response));
+            }
+        }
+
+        private void DeviceSetupComplete(INatDevice device)
+        {
+            OnDeviceFound(new DeviceEventArgs(device));
+        }
+
+        private void OnDeviceFound(DeviceEventArgs args)
+        {
+            if (DeviceFound != null)
+                DeviceFound(this, args);
+        }
+    }
+}

+ 60 - 0
Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs

@@ -0,0 +1,60 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.Text;
+
+namespace Mono.Nat.Upnp
+{
+    internal static class DiscoverDeviceMessage
+    {
+        /// <summary>
+        /// The message sent to discover all uPnP devices on the network
+        /// </summary>
+        /// <returns></returns>
+        public static byte[] EncodeSSDP()
+        {
+            string s = "M-SEARCH * HTTP/1.1\r\n"
+                        + "HOST: 239.255.255.250:1900\r\n"
+                        + "MAN: \"ssdp:discover\"\r\n"
+                        + "MX: 3\r\n"
+                        + "ST: ssdp:all\r\n\r\n";
+            return UTF8Encoding.ASCII.GetBytes(s);
+        }
+
+        public static byte[] EncodeUnicast(IPAddress gatewayAddress)
+        {
+            //Format obtained from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf pg 31
+            //This method only works with upnp 1.1 routers... unfortunately
+            string s = "M-SEARCH * HTTP/1.1\r\n"
+                        + "HOST: " + gatewayAddress + ":1900\r\n"
+                        + "MAN: \"ssdp:discover\"\r\n"
+                        + "ST: ssdp:all\r\n\r\n";
+                        //+ "USER-AGENT: unix/5.1 UPnP/1.1 MyProduct/1.0\r\n\r\n";
+            return UTF8Encoding.ASCII.GetBytes(s);
+        }
+    }
+}

+ 63 - 0
Mono.Nat/Upnp/Messages/ErrorMessage.cs

@@ -0,0 +1,63 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Upnp
+{
+    internal class ErrorMessage : MessageBase
+    {
+        #region Member Variables
+        public string Description
+        {
+            get { return this.description; } 
+        }
+        private string description;
+
+        public int ErrorCode
+        {
+            get { return this.errorCode; }
+        }
+        private int errorCode;
+        #endregion
+
+
+        #region Constructors
+        public ErrorMessage(int errorCode, string description) 
+            :base(null)
+        {
+            this.description = description;
+            this.errorCode = errorCode;
+        }
+        #endregion
+
+
+        public override System.Net.WebRequest Encode(out byte[] body)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 62 - 0
Mono.Nat/Upnp/Messages/GetServicesMessage.cs

@@ -0,0 +1,62 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Diagnostics;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+    internal class GetServicesMessage : MessageBase
+    {
+        private string servicesDescriptionUrl;
+        private EndPoint hostAddress;
+
+        public GetServicesMessage(string description, EndPoint hostAddress)
+            :base(null)
+        {
+            if (string.IsNullOrEmpty(description))
+                Trace.WriteLine("Description is null");
+
+            if (hostAddress == null)
+                Trace.WriteLine("hostaddress is null");
+
+            this.servicesDescriptionUrl = description;
+            this.hostAddress = hostAddress;
+        }
+
+
+        public override WebRequest Encode(out byte[] body)
+        {
+            HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl);
+            req.Headers.Add("ACCEPT-LANGUAGE", "en");
+            req.Method = "GET";
+
+            body = new byte[0];
+            return req;
+        }
+    }
+}

+ 75 - 0
Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs

@@ -0,0 +1,75 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.IO;
+using System.Globalization;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+    internal class CreatePortMappingMessage : MessageBase
+    {
+        #region Private Fields
+
+        private IPAddress localIpAddress;
+        private Mapping mapping;
+
+        #endregion
+
+
+        #region Constructors
+        public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device)
+            : base(device)
+        {
+            this.mapping = mapping;
+            this.localIpAddress = localIpAddress;
+        }
+        #endregion
+
+
+        public override WebRequest Encode(out byte[] body)
+        {
+            CultureInfo culture = CultureInfo.InvariantCulture;
+
+            StringBuilder builder = new StringBuilder(256);
+            XmlWriter writer = CreateWriter(builder);
+
+            WriteFullElement(writer, "NewRemoteHost", string.Empty);
+            WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture));
+            WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
+            WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture));
+            WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString());
+            WriteFullElement(writer, "NewEnabled", "1");
+            WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description);
+            WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString());
+
+            writer.Flush();
+            return CreateRequest("AddPortMapping", builder.ToString(), out body);
+        }
+    }
+}

+ 57 - 0
Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs

@@ -0,0 +1,57 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+	internal class DeletePortMappingMessage : MessageBase
+	{
+		private Mapping mapping;
+
+		public DeletePortMappingMessage(Mapping mapping, UpnpNatDevice device)
+			: base(device)
+		{
+			this.mapping = mapping;
+		}
+
+		public override WebRequest Encode(out byte[] body)
+		{
+			StringBuilder builder = new StringBuilder(256);
+			XmlWriter writer = CreateWriter(builder);
+
+			WriteFullElement(writer, "NewRemoteHost", string.Empty);
+			WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture));
+			WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
+
+			writer.Flush();
+			return CreateRequest("DeletePortMapping", builder.ToString(), out body);
+		}
+	}
+}

+ 51 - 0
Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs

@@ -0,0 +1,51 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using System.IO;
+
+namespace Mono.Nat.Upnp
+{
+    internal class GetExternalIPAddressMessage : MessageBase
+    {
+
+        #region Constructors
+        public GetExternalIPAddressMessage(UpnpNatDevice device)
+            :base(device)
+        {
+        }
+        #endregion
+
+
+        public override WebRequest Encode(out byte[] body)
+        {
+            return CreateRequest("GetExternalIPAddress", string.Empty, out body);
+        }
+    }
+}

+ 55 - 0
Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs

@@ -0,0 +1,55 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+    internal class GetGenericPortMappingEntry : MessageBase
+    {
+        private int index;
+
+        public GetGenericPortMappingEntry(int index, UpnpNatDevice device)
+            :base(device)
+        {
+            this.index = index;
+        }
+
+        public override System.Net.WebRequest Encode(out byte[] body)
+        {
+            StringBuilder sb = new StringBuilder(128);
+            XmlWriter writer = CreateWriter(sb);
+
+            WriteFullElement(writer, "NewPortMappingIndex", index.ToString());
+
+            writer.Flush();
+            return CreateRequest("GetGenericPortMappingEntry", sb.ToString(), out body);
+        }
+    }
+}

+ 60 - 0
Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs

@@ -0,0 +1,60 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+	internal class GetSpecificPortMappingEntryMessage : MessageBase
+	{
+		internal Protocol protocol;
+		internal int externalPort;
+
+		public GetSpecificPortMappingEntryMessage(Protocol protocol, int externalPort, UpnpNatDevice device)
+			: base(device)
+		{
+			this.protocol = protocol;
+			this.externalPort = externalPort;
+		}
+
+		public override WebRequest Encode(out byte[] body)
+		{
+			StringBuilder sb = new StringBuilder(64);
+			XmlWriter writer = CreateWriter(sb);
+
+			WriteFullElement(writer, "NewRemoteHost", string.Empty);
+			WriteFullElement(writer, "NewExternalPort", externalPort.ToString());
+			WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP");
+			writer.Flush();
+
+			return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body);
+		}
+	}
+}

+ 46 - 0
Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs

@@ -0,0 +1,46 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+namespace Mono.Nat.Upnp
+{
+    internal class CreatePortMappingResponseMessage : MessageBase
+    {
+        #region Constructors
+        public CreatePortMappingResponseMessage()
+            :base(null)
+        {
+        }
+        #endregion
+
+        public override System.Net.WebRequest Encode(out byte[] body)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 44 - 0
Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs

@@ -0,0 +1,44 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+namespace Mono.Nat.Upnp
+{
+    internal class DeletePortMapResponseMessage : MessageBase
+    {
+        public DeletePortMapResponseMessage()
+            :base(null)
+        {
+        }
+
+        public override System.Net.WebRequest Encode(out byte[] body)
+        {
+            throw new NotSupportedException();
+        }
+    }
+}

+ 53 - 0
Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs

@@ -0,0 +1,53 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+    internal class GetExternalIPAddressResponseMessage : MessageBase
+    {
+        public IPAddress ExternalIPAddress
+        {
+            get { return this.externalIPAddress; }
+        }
+        private IPAddress externalIPAddress;
+
+        public GetExternalIPAddressResponseMessage(string ip)
+            :base(null)
+        {
+            this.externalIPAddress = IPAddress.Parse(ip);
+        }
+
+        public override WebRequest Encode(out byte[] body)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 108 - 0
Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs

@@ -0,0 +1,108 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+    internal class GetGenericPortMappingEntryResponseMessage : MessageBase
+    {
+        private string remoteHost;
+        private int externalPort;
+        private Protocol protocol;
+        private int internalPort;
+        private string internalClient;
+        private bool enabled;
+        private string portMappingDescription;
+        private int leaseDuration;
+
+        public string RemoteHost
+        {
+            get { return this.remoteHost; }
+        }
+
+        public int ExternalPort
+        {
+            get { return this.externalPort; }
+        }
+
+        public Protocol Protocol
+        {
+            get { return this.protocol; }
+        }
+
+        public int InternalPort
+        {
+            get { return this.internalPort; }
+        }
+
+        public string InternalClient
+        {
+            get { return this.internalClient; }
+        }
+
+        public bool Enabled
+        {
+            get { return this.enabled; }
+        }
+
+        public string PortMappingDescription
+        {
+            get { return this.portMappingDescription; }
+        }
+
+        public int LeaseDuration
+        {
+            get { return this.leaseDuration; }
+        }
+
+
+        public GetGenericPortMappingEntryResponseMessage(XmlNode data, bool genericMapping)
+            : base(null)
+        {
+            remoteHost = (genericMapping) ? data["NewRemoteHost"].InnerText : string.Empty;
+            externalPort = (genericMapping) ? Convert.ToInt32(data["NewExternalPort"].InnerText) : -1;
+            if (genericMapping)
+                protocol = data["NewProtocol"].InnerText.Equals("TCP", StringComparison.InvariantCultureIgnoreCase) ? Protocol.Tcp : Protocol.Udp;
+            else
+                protocol = Protocol.Udp;
+
+            internalPort = Convert.ToInt32(data["NewInternalPort"].InnerText);
+            internalClient = data["NewInternalClient"].InnerText;
+            enabled = data["NewEnabled"].InnerText == "1" ? true : false;
+            portMappingDescription = data["NewPortMappingDescription"].InnerText;
+            leaseDuration = Convert.ToInt32(data["NewLeaseDuration"].InnerText);
+        }
+
+        public override System.Net.WebRequest Encode(out byte[] body)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 132 - 0
Mono.Nat/Upnp/Messages/UpnpMessage.cs

@@ -0,0 +1,132 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Diagnostics;
+using System.Xml;
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Globalization;
+
+namespace Mono.Nat.Upnp
+{
+    internal abstract class MessageBase
+    {
+        internal static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
+        protected UpnpNatDevice device;
+
+        protected MessageBase(UpnpNatDevice device)
+        {
+            this.device = device;
+        }
+
+        protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body)
+        {
+            string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl;
+            NatUtility.Log("Initiating request to: {0}", ss);
+            Uri location = new Uri(ss);
+
+            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(location);
+            req.KeepAlive = false;
+            req.Method = "POST";
+            req.ContentType = "text/xml; charset=\"utf-8\"";
+            req.Headers.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\"");
+
+            string bodyString = "<s:Envelope "
+               + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+               + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+               + "<s:Body>"
+               + "<u:" + upnpMethod + " "
+               + "xmlns:u=\"" + device.ServiceType + "\">"
+               + methodParameters
+               + "</u:" + upnpMethod + ">"
+               + "</s:Body>"
+               + "</s:Envelope>\r\n\r\n";
+
+			body = System.Text.Encoding.UTF8.GetBytes(bodyString);
+            return req;
+        }
+
+        public static MessageBase Decode(UpnpNatDevice device, string message)
+        {
+            XmlNode node;
+            XmlDocument doc = new XmlDocument();
+            doc.LoadXml(message);
+
+            XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
+
+            // Error messages should be found under this namespace
+            nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0");
+            nsm.AddNamespace("responseNs", device.ServiceType);
+
+            // Check to see if we have a fault code message.
+			if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) {
+				string errorCode = node["errorCode"] != null ? node["errorCode"].InnerText : "";
+				string errorDescription = node["errorDescription"] != null ? node["errorDescription"].InnerText : "";
+
+				return new ErrorMessage(Convert.ToInt32(errorCode, CultureInfo.InvariantCulture), errorDescription);
+			}
+
+	        if ((doc.SelectSingleNode("//responseNs:AddPortMappingResponse", nsm)) != null)
+                return new CreatePortMappingResponseMessage();
+
+            if ((doc.SelectSingleNode("//responseNs:DeletePortMappingResponse", nsm)) != null)
+                return new DeletePortMapResponseMessage();
+
+			if ((node = doc.SelectSingleNode("//responseNs:GetExternalIPAddressResponse", nsm)) != null) {
+				string newExternalIPAddress = node["NewExternalIPAddress"] != null ? node["NewExternalIPAddress"].InnerText : "";
+				return new GetExternalIPAddressResponseMessage(newExternalIPAddress);
+			}
+
+	        if ((node = doc.SelectSingleNode("//responseNs:GetGenericPortMappingEntryResponse", nsm)) != null)
+                return new GetGenericPortMappingEntryResponseMessage(node, true);
+
+            if ((node = doc.SelectSingleNode("//responseNs:GetSpecificPortMappingEntryResponse", nsm)) != null)
+                return new GetGenericPortMappingEntryResponseMessage(node, false);
+
+            NatUtility.Log("Unknown message returned. Please send me back the following XML:");
+            NatUtility.Log(message);
+            return null;
+        }
+
+        public abstract WebRequest Encode(out byte[] body);
+
+        internal static void WriteFullElement(XmlWriter writer, string element, string value)
+        {
+            writer.WriteStartElement(element);
+            writer.WriteString(value);
+            writer.WriteEndElement();
+        }
+
+        internal static XmlWriter CreateWriter(StringBuilder sb)
+        {
+            XmlWriterSettings settings = new XmlWriterSettings();
+            settings.ConformanceLevel = ConformanceLevel.Fragment;
+            return XmlWriter.Create(sb, settings);
+        }
+    }
+}

+ 287 - 0
Mono.Nat/Upnp/Searchers/UpnpSearcher.cs

@@ -0,0 +1,287 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Upnp;
+using System.Diagnostics;
+using System.Net.Sockets;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.Dlna;
+
+namespace Mono.Nat
+{
+    internal class UpnpSearcher : ISearcher
+    {
+        private const int SearchPeriod = 5 * 60; // The time in seconds between each search
+		static UpnpSearcher instance = new UpnpSearcher();
+		public static List<UdpClient> sockets = CreateSockets();
+
+		public static UpnpSearcher Instance
+		{
+			get { return instance; }
+		}
+
+        public event EventHandler<DeviceEventArgs> DeviceFound;
+        public event EventHandler<DeviceEventArgs> DeviceLost;
+
+        private List<INatDevice> devices;
+		private Dictionary<IPAddress, DateTime> lastFetched;
+        private DateTime nextSearch;
+        private IPEndPoint searchEndpoint;
+
+        UpnpSearcher()
+        {
+            devices = new List<INatDevice>();
+			lastFetched = new Dictionary<IPAddress, DateTime>();
+            //searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
+            searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
+        }
+
+		static List<UdpClient> CreateSockets()
+		{
+			List<UdpClient> clients = new List<UdpClient>();
+			try
+			{
+				foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces())
+				{
+					foreach (UnicastIPAddressInformation address in n.GetIPProperties().UnicastAddresses)
+					{
+						if (address.Address.AddressFamily == AddressFamily.InterNetwork)
+						{
+							try
+							{
+								clients.Add(new UdpClient(new IPEndPoint(address.Address, 0)));
+							}
+							catch
+							{
+								continue; // Move on to the next address.
+							}
+						}
+					}
+				}
+			}
+			catch (Exception)
+			{
+				clients.Add(new UdpClient(0));
+			}
+			return clients;
+		}
+
+        public void Search()
+		{
+			foreach (UdpClient s in sockets)
+			{
+				try
+				{
+					Search(s);
+				}
+				catch
+				{
+					// Ignore any search errors
+				}
+			}
+		}
+
+        void Search(UdpClient client)
+        {
+            nextSearch = DateTime.Now.AddSeconds(SearchPeriod);
+            byte[] data = DiscoverDeviceMessage.EncodeSSDP();
+
+            // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
+            for (int i = 0; i < 3; i++)
+                client.Send(data, data.Length, searchEndpoint);
+        }
+
+        public IPEndPoint SearchEndpoint
+        {
+            get { return searchEndpoint; }
+        }
+
+        public void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint)
+        {
+            // No matter what, this method should never throw an exception. If something goes wrong
+            // we should still be in a position to handle the next reply correctly.
+            try
+            {
+                /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. 
+				 Any other device type is no good to us for this purpose. See the IGP overview paper 
+				 page 5 for an overview of device types and their hierarchy.
+				 http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+                /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+				 version it is and apply the correct URN. */
+
+                /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+				 prefix. */
+
+                // We have an internet gateway device now
+                UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty);
+
+                if (devices.Contains(d))
+                {
+                    // We already have found this device, so we just refresh it to let people know it's
+                    // Still alive. If a device doesn't respond to a search, we dump it.
+                    devices[devices.IndexOf(d)].LastSeen = DateTime.Now;
+                }
+                else
+                {
+
+                    // If we send 3 requests at a time, ensure we only fetch the services list once
+                    // even if three responses are received
+                    if (lastFetched.ContainsKey(endpoint.Address))
+                    {
+                        DateTime last = lastFetched[endpoint.Address];
+                        if ((DateTime.Now - last) < TimeSpan.FromSeconds(20))
+                            return;
+                    }
+                    lastFetched[endpoint.Address] = DateTime.Now;
+
+                    // Once we've parsed the information we need, we tell the device to retrieve it's service list
+                    // Once we successfully receive the service list, the callback provided will be invoked.
+                    NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
+                    d.GetServicesList(DeviceSetupComplete);
+                }
+            }
+            catch (Exception ex)
+            {
+                NatUtility.Log("Unhandled exception when trying to decode a device's response Send me the following data: ");
+                NatUtility.Log("ErrorMessage:");
+                NatUtility.Log(ex.Message);
+            }
+        }
+
+        public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+        {
+            // Convert it to a string for easy parsing
+            string dataString = null;
+
+            // No matter what, this method should never throw an exception. If something goes wrong
+            // we should still be in a position to handle the next reply correctly.
+            try {
+	            string urn;
+                dataString = Encoding.UTF8.GetString(response);
+
+				if (NatUtility.Verbose)
+					NatUtility.Log("UPnP Response: {0}", dataString);
+
+				/* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. 
+				 Any other device type is no good to us for this purpose. See the IGP overview paper 
+				 page 5 for an overview of device types and their hierarchy.
+				 http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+				/* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+				 version it is and apply the correct URN. */
+
+				/* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+				 prefix. */
+
+                string log = "UPnP Response: Router advertised a '{0}' service";
+                StringComparison c = StringComparison.OrdinalIgnoreCase;
+                if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) {
+	                urn = "urn:schemas-upnp-org:service:WANIPConnection:1";
+	                NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1");
+                } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) {
+					urn = "urn:schemas-upnp-org:service:WANPPPConnection:1";
+					NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:");
+				} else
+					return;
+
+                // We have an internet gateway device now
+                UpnpNatDevice d = new UpnpNatDevice(localAddress, dataString, urn);
+
+                if (devices.Contains(d))
+                {
+                    // We already have found this device, so we just refresh it to let people know it's
+                    // Still alive. If a device doesn't respond to a search, we dump it.
+                    devices[devices.IndexOf(d)].LastSeen = DateTime.Now;
+                }
+                else
+                {
+
+					// If we send 3 requests at a time, ensure we only fetch the services list once
+					// even if three responses are received
+					if (lastFetched.ContainsKey(endpoint.Address))
+					{
+						DateTime last = lastFetched[endpoint.Address];
+						if ((DateTime.Now - last) < TimeSpan.FromSeconds(20))
+							return;
+					}
+					lastFetched[endpoint.Address] = DateTime.Now;
+					
+                    // Once we've parsed the information we need, we tell the device to retrieve it's service list
+                    // Once we successfully receive the service list, the callback provided will be invoked.
+					NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
+                    d.GetServicesList(DeviceSetupComplete);
+                }
+            }
+            catch (Exception ex)
+            {
+                Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: ");
+                Trace.WriteLine("ErrorMessage:");
+                Trace.WriteLine(ex.Message);
+                Trace.WriteLine("Data string:");
+                Trace.WriteLine(dataString);
+            }
+        }
+
+        public DateTime NextSearch
+        {
+            get { return nextSearch; }
+        }
+
+        private void DeviceSetupComplete(INatDevice device)
+        {
+            lock (this.devices)
+            {
+                // We don't want the same device in there twice
+                if (devices.Contains(device))
+                    return;
+
+                devices.Add(device);
+            }
+
+            OnDeviceFound(new DeviceEventArgs(device));
+        }
+
+        private void OnDeviceFound(DeviceEventArgs args)
+        {
+            if (DeviceFound != null)
+                DeviceFound(this, args);
+        }
+
+        public NatProtocol Protocol
+        {
+            get { return NatProtocol.Upnp; }
+        }
+    }
+}

+ 83 - 0
Mono.Nat/Upnp/Upnp.cs

@@ -0,0 +1,83 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//   Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Mono.Nat.Upnp
+{
+    internal class Upnp
+    {
+        public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+        {
+            // Convert it to a string for easy parsing
+            string dataString = null;
+
+
+            string urn;
+            dataString = Encoding.UTF8.GetString(response);
+
+            if (NatUtility.Verbose)
+                NatUtility.Log("UPnP Response: {0}", dataString);
+
+            /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. 
+                Any other device type is no good to us for this purpose. See the IGP overview paper 
+                page 5 for an overview of device types and their hierarchy.
+                http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+            /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+                version it is and apply the correct URN. */
+
+            /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+                prefix. */
+
+            string log = "UPnP Response: Router advertised a '{0}' service";
+            StringComparison c = StringComparison.OrdinalIgnoreCase;
+            if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1)
+            {
+                urn = "urn:schemas-upnp-org:service:WANIPConnection:1";
+                NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1");
+            }
+            else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1)
+            {
+                urn = "urn:schemas-upnp-org:service:WANPPPConnection:1";
+                NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:");
+            }
+            else
+                throw new NotSupportedException("Received non-supported device type");
+
+            // We have an internet gateway device now
+            return new UpnpNatDevice(localAddress, dataString, urn);
+        }
+    }
+}

+ 651 - 0
Mono.Nat/Upnp/UpnpNatDevice.cs

@@ -0,0 +1,651 @@
+//
+// Authors:
+//   Alan McGovern alan.mcgovern@gmail.com
+//   Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Xml;
+using System.Text;
+using System.Diagnostics;
+using MediaBrowser.Controller.Dlna;
+
+namespace Mono.Nat.Upnp
+{
+	public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice> 
+	{
+		private EndPoint hostEndPoint;
+		private IPAddress localAddress;
+		private string serviceDescriptionUrl;
+		private string controlUrl;
+		private string serviceType;
+
+		public override IPAddress LocalAddress
+		{
+			get { return localAddress; }
+		}
+		
+		/// <summary>
+		/// The callback to invoke when we are finished setting up the device
+		/// </summary>
+		private NatDeviceCallback callback;
+
+        internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType)
+        {
+            this.LastSeen = DateTime.Now;
+            this.localAddress = localAddress;
+
+            // Split the string at the "location" section so i can extract the ipaddress and service description url
+            string locationDetails = deviceInfo.Location.ToString();
+            this.serviceType = serviceType;
+
+            // Make sure we have no excess whitespace
+            locationDetails = locationDetails.Trim();
+
+            // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+            // Are we going to get addresses with the "http://" attached?
+            if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+            {
+                NatUtility.Log("Found device at: {0}", locationDetails);
+                // This bit strings out the "http://" from the string
+                locationDetails = locationDetails.Substring(7);
+
+                this.hostEndPoint = hostEndPoint;
+
+                NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
+
+                // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
+                // and port information
+                this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
+            }
+            else
+            {
+                NatUtility.Log("Couldn't decode address. Please send following string to the developer: ");
+            }
+        }
+
+        internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType)
+		{
+			this.LastSeen = DateTime.Now;
+			this.localAddress = localAddress;
+
+			// Split the string at the "location" section so i can extract the ipaddress and service description url
+			string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.InvariantCultureIgnoreCase) + 9).Split('\r')[0];
+            this.serviceType = serviceType;
+
+			// Make sure we have no excess whitespace
+			locationDetails = locationDetails.Trim();
+
+			// FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+			// Are we going to get addresses with the "http://" attached?
+			if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+			{
+				NatUtility.Log("Found device at: {0}", locationDetails);
+				// This bit strings out the "http://" from the string
+				locationDetails = locationDetails.Substring(7);
+
+				// We then split off the end of the string to get something like: 192.168.0.3:241 in our string
+				string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/'));
+
+				// From this we parse out the IP address and Port
+                if (hostAddressAndPort.IndexOf(':') > 0)
+                {
+                    this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))),
+                    Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture));
+                }
+                else
+                {
+                    // there is no port specified, use default port (80)
+                    this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80);
+                }
+
+				NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
+				
+				// The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
+				// and port information
+				this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
+			}
+			else
+			{
+				Trace.WriteLine("Couldn't decode address. Please send following string to the developer: ");
+				Trace.WriteLine(deviceDetails);
+			}
+		}
+
+		/// <summary>
+		/// The EndPoint that the device is at
+		/// </summary>
+		internal EndPoint HostEndPoint
+		{
+			get { return this.hostEndPoint; }
+		}
+
+		/// <summary>
+		/// The relative url of the xml file that describes the list of services is at
+		/// </summary>
+		internal string ServiceDescriptionUrl
+		{
+			get { return this.serviceDescriptionUrl; }
+		}
+
+		/// <summary>
+		/// The relative url that we can use to control the port forwarding
+		/// </summary>
+		internal string ControlUrl
+		{
+			get { return this.controlUrl; }
+		}
+
+		/// <summary>
+		/// The service type we're using on the device
+		/// </summary>
+		public string ServiceType
+		{
+			get { return serviceType; }
+		}
+
+		/// <summary>
+		/// Begins an async call to get the external ip address of the router
+		/// </summary>
+		public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState)
+		{
+			// Create the port map message
+			GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this);
+			return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal);
+		}
+
+		/// <summary>
+		///  Maps the specified port to this computer
+		/// </summary>
+        public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+		{
+            CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
+            return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal);
+		}
+
+		/// <summary>
+		/// Removes a port mapping from this computer  
+		/// </summary>
+		public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+		{
+			DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this);
+			return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal);
+		}
+
+
+		public override IAsyncResult BeginGetAllMappings(AsyncCallback callback, object asyncState)
+		{
+			GetGenericPortMappingEntry message = new GetGenericPortMappingEntry(0, this);
+			return BeginMessageInternal(message, callback, asyncState, EndGetAllMappingsInternal);
+		}
+
+
+		public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState)
+		{
+			GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this);
+			return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal));
+		}
+
+		/// <summary>
+		/// 
+		/// </summary>
+		/// <param name="result"></param>
+		public override void EndCreatePortMap(IAsyncResult result)
+		{
+			if (result == null) throw new ArgumentNullException("result");
+
+			PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+			if (mappingResult == null)
+				throw new ArgumentException("Invalid AsyncResult", "result");
+
+			// Check if we need to wait for the operation to finish
+			if (!result.IsCompleted)
+				result.AsyncWaitHandle.WaitOne();
+
+			// If we have a saved exception, it means something went wrong during the mapping
+			// so we just rethrow the exception and let the user figure out what they should do.
+			if (mappingResult.SavedMessage is ErrorMessage)
+			{
+				ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+				throw new MappingException(msg.ErrorCode, msg.Description);
+			}
+
+			//return result.AsyncState as Mapping;
+		}
+
+
+		/// <summary>
+		/// 
+		/// </summary>
+		/// <param name="result"></param>
+		public override void EndDeletePortMap(IAsyncResult result)
+		{
+			if (result == null)
+				throw new ArgumentNullException("result");
+
+			PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+			if (mappingResult == null)
+				throw new ArgumentException("Invalid AsyncResult", "result");
+
+			// Check if we need to wait for the operation to finish
+			if (!mappingResult.IsCompleted)
+				mappingResult.AsyncWaitHandle.WaitOne();
+
+			// If we have a saved exception, it means something went wrong during the mapping
+			// so we just rethrow the exception and let the user figure out what they should do.
+			if (mappingResult.SavedMessage is ErrorMessage)
+			{
+				ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+				throw new MappingException(msg.ErrorCode, msg.Description);
+			}
+
+			// If all goes well, we just return
+			//return true;
+		}
+
+
+		public override Mapping[] EndGetAllMappings(IAsyncResult result)
+		{
+			if (result == null)
+				throw new ArgumentNullException("result");
+
+			GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
+			if (mappingResult == null)
+				throw new ArgumentException("Invalid AsyncResult", "result");
+
+			if (!mappingResult.IsCompleted)
+				mappingResult.AsyncWaitHandle.WaitOne();
+
+			if (mappingResult.SavedMessage is ErrorMessage)
+			{
+				ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+				if (msg.ErrorCode != 713)
+					throw new MappingException(msg.ErrorCode, msg.Description);
+			}
+
+			return mappingResult.Mappings.ToArray();
+		}
+
+
+		/// <summary>
+		/// Ends an async request to get the external ip address of the router
+		/// </summary>
+		public override IPAddress EndGetExternalIP(IAsyncResult result)
+		{
+			if (result == null) throw new ArgumentNullException("result");
+
+			PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+			if (mappingResult == null)
+				throw new ArgumentException("Invalid AsyncResult", "result");
+
+			if (!result.IsCompleted)
+				result.AsyncWaitHandle.WaitOne();
+
+			if (mappingResult.SavedMessage is ErrorMessage)
+			{
+				ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+				throw new MappingException(msg.ErrorCode, msg.Description);
+			}
+
+			if (mappingResult.SavedMessage == null)
+				return null;
+			else
+				return ((GetExternalIPAddressResponseMessage)mappingResult.SavedMessage).ExternalIPAddress;
+		}
+
+
+		public override Mapping EndGetSpecificMapping(IAsyncResult result)
+		{
+			if (result == null)
+				throw new ArgumentNullException("result");
+
+			GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
+			if (mappingResult == null)
+				throw new ArgumentException("Invalid AsyncResult", "result");
+
+			if (!mappingResult.IsCompleted)
+				mappingResult.AsyncWaitHandle.WaitOne();
+
+			if (mappingResult.SavedMessage is ErrorMessage)
+			{
+				ErrorMessage message = mappingResult.SavedMessage as ErrorMessage;
+				if (message.ErrorCode != 0x2ca)
+				{
+					throw new MappingException(message.ErrorCode, message.Description);
+				}
+			}
+			if (mappingResult.Mappings.Count == 0)
+				return new Mapping (Protocol.Tcp, -1, -1);
+
+			return mappingResult.Mappings[0];
+		}
+
+
+		public override bool Equals(object obj)
+		{
+			UpnpNatDevice device = obj as UpnpNatDevice;
+			return (device == null) ? false : this.Equals((device));
+		}
+
+
+		public bool Equals(UpnpNatDevice other)
+		{
+			return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint)
+				//&& this.controlUrl == other.controlUrl
+				&& this.serviceDescriptionUrl == other.serviceDescriptionUrl);
+		}
+
+		public override int GetHashCode()
+		{
+			return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode());
+		}
+
+		private IAsyncResult BeginMessageInternal(MessageBase message, AsyncCallback storedCallback, object asyncState, AsyncCallback callback)
+		{
+			byte[] body;
+			WebRequest request = message.Encode(out body);
+			PortMapAsyncResult mappingResult = PortMapAsyncResult.Create(message, request, storedCallback, asyncState);
+
+			if (body.Length > 0)
+			{
+				request.ContentLength = body.Length;
+				request.BeginGetRequestStream(delegate(IAsyncResult result) {
+					try
+					{
+						Stream s = request.EndGetRequestStream(result);
+						s.Write(body, 0, body.Length);
+						request.BeginGetResponse(callback, mappingResult);
+					}
+					catch (Exception ex)
+					{
+						mappingResult.Complete(ex);
+					}
+				}, null);
+			}
+			else
+			{
+				request.BeginGetResponse(callback, mappingResult);
+			}
+			return mappingResult;
+		}
+
+		private void CompleteMessage(IAsyncResult result)
+		{
+			PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
+			mappingResult.CompletedSynchronously = result.CompletedSynchronously;
+            mappingResult.Complete();
+		}
+
+		private MessageBase DecodeMessageFromResponse(Stream s, long length)
+		{
+			StringBuilder data = new StringBuilder();
+			int bytesRead = 0;
+			int totalBytesRead = 0;
+			byte[] buffer = new byte[10240];
+
+			// Read out the content of the message, hopefully picking everything up in the case where we have no contentlength
+			if (length != -1)
+			{
+				while (totalBytesRead < length)
+				{
+					bytesRead = s.Read(buffer, 0, buffer.Length);
+					data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+					totalBytesRead += bytesRead;
+				}
+			}
+			else
+			{
+				while ((bytesRead = s.Read(buffer, 0, buffer.Length)) != 0)
+					data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+			}
+
+			// Once we have our content, we need to see what kind of message it is. It'll either a an error
+			// or a response based on the action we performed.
+			return MessageBase.Decode(this, data.ToString());
+		}
+
+		private void EndCreatePortMapInternal(IAsyncResult result)
+		{
+			EndMessageInternal(result);
+			CompleteMessage(result);
+		}
+
+		private void EndMessageInternal(IAsyncResult result)
+		{
+			HttpWebResponse response = null;
+			PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
+
+			try
+			{
+				try
+				{
+					response = (HttpWebResponse)mappingResult.Request.EndGetResponse(result);
+				}
+				catch (WebException ex)
+				{
+					// Even if the request "failed" i want to continue on to read out the response from the router
+					response = ex.Response as HttpWebResponse;
+					if (response == null)
+						mappingResult.SavedMessage = new ErrorMessage((int)ex.Status, ex.Message);
+				}
+				if (response != null)
+					mappingResult.SavedMessage = DecodeMessageFromResponse(response.GetResponseStream(), response.ContentLength);
+			}
+
+			finally
+			{
+				if (response != null)
+					response.Close();
+			}
+		}
+
+		private void EndDeletePortMapInternal(IAsyncResult result)
+		{
+			EndMessageInternal(result);
+			CompleteMessage(result);
+		}
+
+		private void EndGetAllMappingsInternal(IAsyncResult result)
+		{
+			EndMessageInternal(result);
+
+			GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
+			GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
+			if (message != null)
+			{
+				Mapping mapping = new Mapping (message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration);
+				mapping.Description = message.PortMappingDescription;
+				mappingResult.Mappings.Add(mapping);
+				GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this);
+
+				// It's ok to do this synchronously because we should already be on anther thread
+				// and this won't block the user.
+				byte[] body;
+				WebRequest request = next.Encode(out body);
+				if (body.Length > 0)
+				{
+					request.ContentLength = body.Length;
+					request.GetRequestStream().Write(body, 0, body.Length);
+				}
+				mappingResult.Request = request;
+				request.BeginGetResponse(EndGetAllMappingsInternal, mappingResult);
+				return;
+			}
+
+			CompleteMessage(result);
+		}
+
+		private void EndGetExternalIPInternal(IAsyncResult result)
+		{
+			EndMessageInternal(result);
+			CompleteMessage(result);
+		}
+
+		private void EndGetSpecificMappingInternal(IAsyncResult result)
+		{
+			EndMessageInternal(result);
+
+			GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
+			GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
+			if (message != null) {
+				Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration);
+				mapping.Description = mappingResult.SpecificMapping.Description;
+				mappingResult.Mappings.Add(mapping);
+			}
+
+			CompleteMessage(result);
+		}
+
+		internal void GetServicesList(NatDeviceCallback callback)
+		{
+			// Save the callback so i can use it again later when i've finished parsing the services available
+			this.callback = callback;
+
+			// Create a HTTPWebRequest to download the list of services the device offers
+			byte[] body;
+			WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint).Encode(out body);
+			if (body.Length > 0)
+				NatUtility.Log("Error: Services Message contained a body");
+			request.BeginGetResponse(this.ServicesReceived, request);
+		}
+
+		private void ServicesReceived(IAsyncResult result)
+		{
+			HttpWebResponse response = null;
+			try
+			{
+				int abortCount = 0;
+				int bytesRead = 0;
+				byte[] buffer = new byte[10240];
+				StringBuilder servicesXml = new StringBuilder();
+				XmlDocument xmldoc = new XmlDocument();
+				HttpWebRequest request = result.AsyncState as HttpWebRequest;
+				response = request.EndGetResponse(result) as HttpWebResponse;
+				Stream s = response.GetResponseStream();
+
+				if (response.StatusCode != HttpStatusCode.OK) {
+					NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode);
+					return; // FIXME: This the best thing to do??
+				}
+
+				while (true)
+				{
+					bytesRead = s.Read(buffer, 0, buffer.Length);
+					servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+					try
+					{
+						xmldoc.LoadXml(servicesXml.ToString());
+						response.Close();
+						break;
+					}
+					catch (XmlException)
+					{
+						// If we can't receive the entire XML within 500ms, then drop the connection
+						// Unfortunately not all routers supply a valid ContentLength (mine doesn't)
+						// so this hack is needed to keep testing our recieved data until it gets successfully
+						// parsed by the xmldoc. Without this, the code will never pick up my router.
+						if (abortCount++ > 50)
+						{
+						    response.Close();
+						    return;
+						}
+						NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint);
+						System.Threading.Thread.Sleep(10);
+					}
+				}
+
+				NatUtility.Log("{0}: Parsed services list", HostEndPoint);
+				XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable);
+				ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0");
+				XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns);
+
+				foreach (XmlNode node in nodes)
+				{
+					//Go through each service there
+					foreach (XmlNode service in node.ChildNodes)
+					{
+						//If the service is a WANIPConnection, then we have what we want
+                        string type = service["serviceType"].InnerText;
+						NatUtility.Log("{0}: Found service: {1}", HostEndPoint, type);
+                        StringComparison c = StringComparison.OrdinalIgnoreCase;
+						// TODO: Add support for version 2 of UPnP.
+						if (type.Equals("urn:schemas-upnp-org:service:WANPPPConnection:1", c) ||
+							type.Equals("urn:schemas-upnp-org:service:WANIPConnection:1", c))
+						{
+							this.controlUrl = service["controlURL"].InnerText;
+							NatUtility.Log("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl);
+							try
+							{
+								Uri u = new Uri(controlUrl);
+								if (u.IsAbsoluteUri)
+								{
+									EndPoint old = hostEndPoint;
+									this.hostEndPoint = new IPEndPoint(IPAddress.Parse(u.Host), u.Port);
+									NatUtility.Log("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint);
+									this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length);
+									NatUtility.Log("{0}: New control url: {1}", HostEndPoint, controlUrl);
+								}
+							}
+							catch
+							{
+								NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl);
+							}
+							NatUtility.Log("{0}: Handshake Complete", HostEndPoint);
+							this.callback(this);
+							return;
+						}
+					}
+				}
+
+				//If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding
+				//So we don't invoke the callback, so this device is never added to our lists
+			}
+			catch (WebException ex)
+			{
+				// Just drop the connection, FIXME: Should i retry?
+				NatUtility.Log("{0}: Device denied the connection attempt: {1}", HostEndPoint, ex);
+			}
+			finally
+			{
+				if (response != null)
+					response.Close();
+			}
+		}
+
+        /// <summary>
+        /// Overridden.
+        /// </summary>
+        /// <returns></returns>
+        public override string ToString( )
+        {
+            //GetExternalIP is blocking and can throw exceptions, can't use it here.
+            return String.Format( 
+                "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}",
+                this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen);
+        }
+	}
+}