浏览代码

update port mapper

Luke Pulverenti 8 年之前
父节点
当前提交
ce043225c4

+ 28 - 9
MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -8,6 +8,7 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Net;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Server.Implementations.Threading;
 
@@ -17,18 +18,20 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
     {
         private readonly IServerApplicationHost _appHost;
         private readonly ILogger _logger;
+        private readonly IHttpClient _httpClient;
         private readonly IServerConfigurationManager _config;
         private readonly IDeviceDiscovery _deviceDiscovery;
 
         private PeriodicTimer _timer;
         private bool _isStarted;
 
-        public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery)
+        public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient)
         {
             _logger = logmanager.GetLogger("PortMapper");
             _appHost = appHost;
             _config = config;
             _deviceDiscovery = deviceDiscovery;
+            _httpClient = httpClient;
         }
 
         private string _lastConfigIdentifier;
@@ -63,6 +66,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
         public void Run()
         {
             NatUtility.Logger = _logger;
+            NatUtility.HttpClient = _httpClient;
 
             if (_config.Configuration.EnableUPnP)
             {
@@ -136,7 +140,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
                 _usnsHandled.Add(identifier);
             }
 
-            _logger.Debug("Calling Nat.Handle on " + identifier);
+            _logger.Debug("Found NAT device: " + identifier);
 
             IPAddress address;
             if (IPAddress.TryParse(info.Location.Host, out address))
@@ -150,16 +154,23 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
                 {
                     var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
 
-                    if (!IPAddress.TryParse(localAddressString, out localAddress))
+                    Uri uri;
+                    if (Uri.TryCreate(localAddressString, UriKind.Absolute, out uri))
                     {
-                        return;
+                        localAddressString = uri.Host;
+
+                        if (!IPAddress.TryParse(localAddressString, out localAddress))
+                        {
+                            return;
+                        }
                     }
                 }
-                catch
+                catch (Exception ex)
                 {
                     return;
                 }
 
+                _logger.Debug("Calling Nat.Handle on " + identifier);
                 NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp);
             }
         }
@@ -229,13 +240,21 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             }
         }
 
-        private void CreatePortMap(INatDevice device, int privatePort, int publicPort)
+        private async void CreatePortMap(INatDevice device, int privatePort, int publicPort)
         {
             _logger.Debug("Creating port map on port {0}", privatePort);
-            device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
+
+            try
+            {
+                await device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
+                {
+                    Description = _appHost.Name
+                }).ConfigureAwait(false);
+            }
+            catch (Exception ex)
             {
-                Description = _appHost.Name
-            });
+                _logger.ErrorException("Error creating port map", ex);
+            }
         }
 
         // As I said before, this method will be never invoked. You can remove it.

+ 2 - 5
Mono.Nat/AbstractNatDevice.cs

@@ -30,6 +30,7 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Net;
+using System.Threading.Tasks;
 
 namespace Mono.Nat
 {
@@ -50,11 +51,7 @@ namespace Mono.Nat
 			set { lastSeen = value; }
 		}
 
-		public virtual void CreatePortMap (Mapping mapping)
-		{
-			IAsyncResult result = BeginCreatePortMap (mapping, null, null);
-		    EndCreatePortMap(result);
-		}
+	    public abstract Task CreatePortMap(Mapping mapping);
 
 		public virtual void DeletePortMap (Mapping mapping)
 		{

+ 2 - 1
Mono.Nat/INatDevice.cs

@@ -30,12 +30,13 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Net;
+using System.Threading.Tasks;
 
 namespace Mono.Nat
 {
 	public interface INatDevice
 	{
-		void CreatePortMap (Mapping mapping);
+		Task CreatePortMap (Mapping mapping);
 		void DeletePortMap (Mapping mapping);
 		
 		IPAddress LocalAddress { get; }

+ 4 - 1
Mono.Nat/Mono.Nat.csproj

@@ -54,7 +54,6 @@
     <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" />
@@ -80,6 +79,10 @@
     <Compile Include="Upnp\UpnpNatDevice.cs" />
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Name>MediaBrowser.Common</Name>
+    </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
       <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
       <Name>MediaBrowser.Controller</Name>

+ 15 - 28
Mono.Nat/NatUtility.cs

@@ -34,9 +34,11 @@ using System.Linq;
 using System.Collections.Generic;
 using System.IO;
 using System.Net.NetworkInformation;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Model.Logging;
 using Mono.Nat.Pmp.Mappers;
+using Mono.Nat.Upnp;
 using Mono.Nat.Upnp.Mappers;
 
 namespace Mono.Nat
@@ -55,8 +57,9 @@ namespace Mono.Nat
         public static List<NatProtocol> EnabledProtocols { get; set; }
 
 	    public static ILogger Logger { get; set; }
+        public static IHttpClient HttpClient { get; set; }
 
-	    public static bool Verbose
+        public static bool Verbose
 		{
 			get { return verbose; }
 			set { verbose = value; }
@@ -153,32 +156,6 @@ namespace Mono.Nat
 		{
             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(Logger);
-                    mapper.DeviceFound += (sender, args) =>
-                    {
-                        if (DeviceFound != null)
-                            DeviceFound(sender, args);
-                    };
-                    mapper.Map(gatewayAddress);                    
-                    break;
-                default:
-                    throw new InvalidOperationException("Unsuported type given");
-
-            }
-            searching.Reset();
-            
-        }
 		
 		//checks if an IP address is a private address space as defined by RFC 1918
 		public static bool IsPrivateAddressSpace (IPAddress address)
@@ -217,11 +194,21 @@ namespace Mono.Nat
             switch (protocol)
             {
                 case NatProtocol.Upnp:
-                    new UpnpSearcher(Logger).Handle(localAddress, deviceInfo, endpoint);
+                    var searcher = new UpnpSearcher(Logger, HttpClient);
+                    searcher.DeviceFound += Searcher_DeviceFound;
+                    searcher.Handle(localAddress, deviceInfo, endpoint);
                     break;
                 default:
                     throw new ArgumentException("Unexpected protocol: " + protocol);
             }
         }
+
+        private static void Searcher_DeviceFound(object sender, DeviceEventArgs e)
+        {
+            if (DeviceFound != null)
+            {
+                DeviceFound(sender, e);
+            }
+        }
     }
 }

+ 1 - 15
Mono.Nat/Pmp/Mappers/PmpMapper.cs

@@ -34,26 +34,12 @@ using Mono.Nat.Pmp;
 
 namespace Mono.Nat.Pmp.Mappers
 {
-    internal class PmpMapper : Pmp, IMapper
+    internal class PmpMapper : 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)

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

@@ -1,118 +0,0 @@
-//
-// 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.
-            }
-        }
-    }
-}

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

@@ -30,6 +30,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Threading;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 
 namespace Mono.Nat.Pmp
 {
@@ -56,6 +57,12 @@ namespace Mono.Nat.Pmp
 			return publicAddress;
 		}
 
+	    public override Task CreatePortMap(Mapping mapping)
+	    {
+            CreatePortMap(mapping, true);
+	        return Task.FromResult(true);
+	    }
+
         public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
 		{
 			PortMapAsyncResult pmar = new PortMapAsyncResult (mapping.Protocol, mapping.PublicPort, PmpConstants.DefaultLeaseTime, callback, asyncState);

+ 78 - 1
Mono.Nat/Pmp/Searchers/PmpSearcher.cs

@@ -40,7 +40,7 @@ using System.Linq;
 
 namespace Mono.Nat
 {
-    internal class PmpSearcher : Pmp.Pmp, ISearcher
+    internal class PmpSearcher : ISearcher
     {
 		static PmpSearcher instance = new PmpSearcher();
         
@@ -60,6 +60,83 @@ namespace Mono.Nat
             CreateSocketsAndAddGateways();
         }
 
+        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.
+            }
+        }
+
         PmpSearcher()
         {
             timeout = 250;

+ 18 - 9
Mono.Nat/Upnp/Mappers/UpnpMapper.cs

@@ -32,19 +32,20 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text;
 using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
 
 namespace Mono.Nat.Upnp.Mappers
 {
     internal class UpnpMapper : Upnp, IMapper
     {
-
         public event EventHandler<DeviceEventArgs> DeviceFound;
 
         public UdpClient Client { get; set; }
 
-        public UpnpMapper(ILogger logger)
-            : base(logger)
+        public UpnpMapper(ILogger logger, IHttpClient httpClient)
+            : base(logger,  httpClient)
         {
             //Bind to local port 1900 for ssdp responses
             Client = new UdpClient(1900);
@@ -60,7 +61,7 @@ namespace Mono.Nat.Upnp.Mappers
             new Thread(Receive).Start(); 
         }
 
-        public void Receive()
+        public async void Receive()
         {
             while (true)
             {
@@ -69,28 +70,36 @@ namespace Mono.Nat.Upnp.Mappers
                 {
                     IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address;
                     byte[] data = Client.Receive(ref received);
-                    Handle(localAddress, data, received);
+
+                    await Handle(localAddress, data, received);
                 }
             }
         }
 
         public void Handle(IPAddress localAddres, byte[] response)
         {
-            Handle(localAddres, response, null);
         }
 
-        public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+        public override async Task<UpnpNatDevice> 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);
+                var d = await base.Handle(localAddress, response, endpoint).ConfigureAwait(false);               
+                var result = await d.GetServicesList().ConfigureAwait(false);
+
+                if (result)
+                {
+                    DeviceSetupComplete(d);
+                }
+
+                return d;
             }
             catch (Exception ex)
             {
                 Logger.ErrorException("Error mapping port. Data string: {0}", ex, Encoding.UTF8.GetString(response));
+                return null;
             }
         }
 

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

@@ -25,6 +25,7 @@
 //
 
 using System;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -54,6 +55,10 @@ namespace Mono.Nat.Upnp
         }
         #endregion
 
+        public override HttpRequestOptions Encode()
+        {
+            throw new NotImplementedException();
+        }
 
         public override System.Net.WebRequest Encode(out byte[] body)
         {

+ 20 - 1
Mono.Nat/Upnp/Messages/GetServicesMessage.cs

@@ -27,6 +27,7 @@
 using System;
 using System.Diagnostics;
 using System.Net;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
 
 namespace Mono.Nat.Upnp
@@ -38,7 +39,7 @@ namespace Mono.Nat.Upnp
         private readonly ILogger _logger;
 
         public GetServicesMessage(string description, EndPoint hostAddress, ILogger logger)
-            :base(null)
+            : base(null)
         {
             if (string.IsNullOrEmpty(description))
                 _logger.Warn("Description is null");
@@ -51,6 +52,13 @@ namespace Mono.Nat.Upnp
             _logger = logger;
         }
 
+        public override string Method
+        {
+            get
+            {
+                return "GET";
+            }
+        }
 
         public override WebRequest Encode(out byte[] body)
         {
@@ -61,5 +69,16 @@ namespace Mono.Nat.Upnp
             body = new byte[0];
             return req;
         }
+
+
+        public override HttpRequestOptions Encode()
+        {
+            var req = new HttpRequestOptions();
+
+            req.Url = "http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl;
+            req.RequestHeaders.Add("ACCEPT-LANGUAGE", "en");
+
+            return req;
+        }
     }
 }

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

@@ -29,6 +29,7 @@ using System.IO;
 using System.Globalization;
 using System.Text;
 using System.Xml;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -51,6 +52,25 @@ namespace Mono.Nat.Upnp
         }
         #endregion
 
+        public override HttpRequestOptions Encode()
+        {
+            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());
+        }
 
         public override WebRequest Encode(out byte[] body)
         {

+ 15 - 1
Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs

@@ -28,6 +28,7 @@ using System.Net;
 using System.IO;
 using System.Text;
 using System.Xml;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -41,7 +42,20 @@ namespace Mono.Nat.Upnp
 			this.mapping = mapping;
 		}
 
-		public override WebRequest Encode(out byte[] body)
+	    public override HttpRequestOptions Encode()
+	    {
+            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());
+        }
+
+        public override WebRequest Encode(out byte[] body)
 		{
 			StringBuilder builder = new StringBuilder(256);
 			XmlWriter writer = CreateWriter(builder);

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

@@ -29,6 +29,7 @@ using System.Collections.Generic;
 using System.Text;
 using System.Net;
 using System.IO;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -42,6 +43,10 @@ namespace Mono.Nat.Upnp
         }
         #endregion
 
+        public override HttpRequestOptions Encode()
+        {
+            return CreateRequest("GetExternalIPAddress", string.Empty);
+        }
 
         public override WebRequest Encode(out byte[] body)
         {

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

@@ -28,6 +28,7 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Xml;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -41,6 +42,17 @@ namespace Mono.Nat.Upnp
             this.index = index;
         }
 
+        public override HttpRequestOptions Encode()
+        {
+            StringBuilder sb = new StringBuilder(128);
+            XmlWriter writer = CreateWriter(sb);
+
+            WriteFullElement(writer, "NewPortMappingIndex", index.ToString());
+
+            writer.Flush();
+            return CreateRequest("GetGenericPortMappingEntry", sb.ToString());
+        }
+
         public override System.Net.WebRequest Encode(out byte[] body)
         {
             StringBuilder sb = new StringBuilder(128);

+ 16 - 2
Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs

@@ -29,6 +29,7 @@ using System.Collections.Generic;
 using System.Text;
 using System.Xml;
 using System.Net;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -55,6 +56,19 @@ namespace Mono.Nat.Upnp
 			writer.Flush();
 
 			return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body);
-		}
-	}
+        }
+
+        public override HttpRequestOptions Encode()
+        {
+            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());
+        }
+    }
 }

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

@@ -27,6 +27,8 @@
 
 
 using System;
+using MediaBrowser.Common.Net;
+
 namespace Mono.Nat.Upnp
 {
     internal class CreatePortMappingResponseMessage : MessageBase
@@ -38,6 +40,11 @@ namespace Mono.Nat.Upnp
         }
         #endregion
 
+        public override HttpRequestOptions Encode()
+        {
+            throw new NotImplementedException();
+        }
+
         public override System.Net.WebRequest Encode(out byte[] body)
         {
             throw new NotImplementedException();

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

@@ -27,6 +27,8 @@
 
 
 using System;
+using MediaBrowser.Common.Net;
+
 namespace Mono.Nat.Upnp
 {
     internal class DeletePortMapResponseMessage : MessageBase
@@ -36,6 +38,11 @@ namespace Mono.Nat.Upnp
         {
         }
 
+        public override HttpRequestOptions Encode()
+        {
+            throw new NotSupportedException();
+        }
+
         public override System.Net.WebRequest Encode(out byte[] body)
         {
             throw new NotSupportedException();

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

@@ -28,6 +28,7 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Net;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -45,6 +46,11 @@ namespace Mono.Nat.Upnp
             this.externalIPAddress = IPAddress.Parse(ip);
         }
 
+        public override HttpRequestOptions Encode()
+        {
+            throw new NotImplementedException();
+        }
+
         public override WebRequest Encode(out byte[] body)
         {
             throw new NotImplementedException();

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

@@ -28,6 +28,7 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Xml;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -100,6 +101,11 @@ namespace Mono.Nat.Upnp
             leaseDuration = Convert.ToInt32(data["NewLeaseDuration"].InnerText);
         }
 
+        public override HttpRequestOptions Encode()
+        {
+            throw new NotImplementedException();
+        }
+
         public override System.Net.WebRequest Encode(out byte[] body)
         {
             throw new NotImplementedException();

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

@@ -31,6 +31,7 @@ using System.Net;
 using System.IO;
 using System.Text;
 using System.Globalization;
+using MediaBrowser.Common.Net;
 
 namespace Mono.Nat.Upnp
 {
@@ -71,6 +72,32 @@ namespace Mono.Nat.Upnp
             return req;
         }
 
+        protected HttpRequestOptions CreateRequest(string upnpMethod, string methodParameters)
+        {
+            string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl;
+            NatUtility.Log("Initiating request to: {0}", ss);
+
+            var req = new HttpRequestOptions();
+            req.Url = ss;
+            req.EnableKeepAlive = false;
+            req.RequestContentType = "text/xml; charset=\"utf-8\"";
+            req.RequestHeaders.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";
+
+            req.RequestContentBytes = System.Text.Encoding.UTF8.GetBytes(bodyString);
+            return req;
+        }
+
         public static MessageBase Decode(UpnpNatDevice device, string message)
         {
             XmlNode node;
@@ -113,8 +140,14 @@ namespace Mono.Nat.Upnp
             return null;
         }
 
+        public abstract HttpRequestOptions Encode();
         public abstract WebRequest Encode(out byte[] body);
 
+        public virtual string Method
+        {
+            get { return "POST"; }
+        }
+
         internal static void WriteFullElement(XmlWriter writer, string element, string value)
         {
             writer.WriteStartElement(element);

+ 5 - 2
Mono.Nat/Upnp/Searchers/UpnpSearcher.cs

@@ -36,6 +36,7 @@ using Mono.Nat.Upnp;
 using System.Diagnostics;
 using System.Net.Sockets;
 using System.Net.NetworkInformation;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Model.Logging;
 
@@ -48,10 +49,12 @@ namespace Mono.Nat
 
         private DateTime nextSearch;
         private readonly ILogger _logger;
+        private readonly IHttpClient _httpClient;
 
-        public UpnpSearcher(ILogger logger)
+        public UpnpSearcher(ILogger logger, IHttpClient httpClient)
         {
             _logger = logger;
+            _httpClient = httpClient;
         }
 
         public void Search()
@@ -76,7 +79,7 @@ namespace Mono.Nat
 				 prefix. */
 
                 // We have an internet gateway device now
-                UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty, _logger);
+                UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty, _logger, _httpClient);
 
                 NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
                 OnDeviceFound(new DeviceEventArgs(d));

+ 8 - 3
Mono.Nat/Upnp/Upnp.cs

@@ -33,6 +33,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Net;
 using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
 
 namespace Mono.Nat.Upnp
@@ -40,13 +42,15 @@ namespace Mono.Nat.Upnp
     internal class Upnp
     {
         protected readonly ILogger Logger;
+        protected readonly IHttpClient HttpClient;
 
-        public Upnp(ILogger logger)
+        public Upnp(ILogger logger, IHttpClient httpClient)
         {
             Logger = logger;
+            HttpClient = httpClient;
         }
 
-        public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+        public virtual Task<UpnpNatDevice> Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
         {
             // Convert it to a string for easy parsing
             string dataString = null;
@@ -85,7 +89,8 @@ namespace Mono.Nat.Upnp
                 throw new NotSupportedException("Received non-supported device type");
 
             // We have an internet gateway device now
-            return new UpnpNatDevice(localAddress, dataString, urn, Logger);
+            var device = new UpnpNatDevice(localAddress, dataString, urn, Logger, HttpClient);
+            return Task.FromResult(device);
         }
     }
 }

+ 525 - 541
Mono.Nat/Upnp/UpnpNatDevice.cs

@@ -32,31 +32,29 @@ using System.Net;
 using System.Xml;
 using System.Text;
 using System.Diagnostics;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Model.Logging;
 
 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 sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice>
+    {
+        private EndPoint hostEndPoint;
+        private IPAddress localAddress;
+        private string serviceDescriptionUrl;
+        private string controlUrl;
+        private string serviceType;
         private readonly ILogger _logger;
+        private readonly IHttpClient _httpClient;
 
         public override IPAddress LocalAddress
-		{
-			get { return localAddress; }
-		}
-		
-		/// <summary>
-		/// The callback to invoke when we are finished setting up the device
-		/// </summary>
-		private NatDeviceCallback callback;
+        {
+            get { return localAddress; }
+        }
 
-        internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger)
+        internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger, IHttpClient httpClient)
         {
             this.LastSeen = DateTime.Now;
             this.localAddress = localAddress;
@@ -65,6 +63,7 @@ namespace Mono.Nat.Upnp
             string locationDetails = deviceInfo.Location.ToString();
             this.serviceType = serviceType;
             _logger = logger;
+            _httpClient = httpClient;
 
             // Make sure we have no excess whitespace
             locationDetails = locationDetails.Trim();
@@ -91,31 +90,32 @@ namespace Mono.Nat.Upnp
             }
         }
 
-        internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger)
-		{
+        internal UpnpNatDevice(IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger, IHttpClient httpClient)
+        {
             _logger = logger;
+            _httpClient = httpClient;
             this.LastSeen = DateTime.Now;
-			this.localAddress = localAddress;
+            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];
+            // 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();
+            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);
+            // 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('/'));
+                // 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
+                // From this we parse out the IP address and Port
                 if (hostAddressAndPort.IndexOf(':') > 0)
                 {
                     this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))),
@@ -127,528 +127,512 @@ namespace Mono.Nat.Upnp
                     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
-			{
+                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
+            {
                 logger.Warn("Couldn't decode address: " + 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>
+            }
+        }
+
+        /// <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;
+        }
+
+        public override Task CreatePortMap(Mapping mapping)
+        {
+            CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
+            return _httpClient.SendAsync(message.Encode(), message.Method);
+        }
+
+        /// <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, _logger).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();
-			}
-		}
+        }
+
+        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 async Task<bool> GetServicesList()
+        {
+            // Create a HTTPWebRequest to download the list of services the device offers
+            var requestOptions = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger).Encode();
+
+            requestOptions.BufferContent = false;
+
+            using (var response = await _httpClient.Get(requestOptions).ConfigureAwait(false))
+            {
+                return ServicesReceived(response);
+            }
+        }
+
+        private bool ServicesReceived(Stream s)
+        {
+            int abortCount = 0;
+            int bytesRead = 0;
+            byte[] buffer = new byte[10240];
+            StringBuilder servicesXml = new StringBuilder();
+            XmlDocument xmldoc = new XmlDocument();
+
+            while (true)
+            {
+                bytesRead = s.Read(buffer, 0, buffer.Length);
+                servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+                try
+                {
+                    xmldoc.LoadXml(servicesXml.ToString());
+                    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)
+                    {
+                        return false;
+                    }
+                    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);
+                        return true;
+                    }
+                }
+            }
+
+            //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
+            return false;
+        }
 
         /// <summary>
         /// Overridden.
         /// </summary>
         /// <returns></returns>
-        public override string ToString( )
+        public override string ToString()
         {
             //GetExternalIP is blocking and can throw exceptions, can't use it here.
-            return String.Format( 
+            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);
         }
-	}
+    }
 }