浏览代码

fixes #2499 - UPnP not working in Emby, but works in other applications

Luke Pulverenti 7 年之前
父节点
当前提交
86226ff97c

+ 1 - 0
Mono.Nat/ISearcher.cs

@@ -33,6 +33,7 @@ using System.Collections.Generic;
 using System.Text;
 using System.Text;
 using System.Net.Sockets;
 using System.Net.Sockets;
 using System.Net;
 using System.Net;
+using System.Threading.Tasks;
 
 
 namespace Mono.Nat
 namespace Mono.Nat
 {
 {

+ 2 - 2
Mono.Nat/NatUtility.cs

@@ -208,14 +208,14 @@ namespace Mono.Nat
             }
             }
         }
         }
 
 
-        public static void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol)
+        public static async Task Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol)
         {
         {
             switch (protocol)
             switch (protocol)
             {
             {
                 case NatProtocol.Upnp:
                 case NatProtocol.Upnp:
                     var searcher = new UpnpSearcher(Logger, HttpClient);
                     var searcher = new UpnpSearcher(Logger, HttpClient);
                     searcher.DeviceFound += Searcher_DeviceFound;
                     searcher.DeviceFound += Searcher_DeviceFound;
-                    searcher.Handle(localAddress, deviceInfo, endpoint);
+                    await searcher.Handle(localAddress, deviceInfo, endpoint).ConfigureAwait(false);
                     break;
                     break;
                 default:
                 default:
                     throw new ArgumentException("Unexpected protocol: " + protocol);
                     throw new ArgumentException("Unexpected protocol: " + protocol);

+ 26 - 26
Mono.Nat/Pmp/Searchers/PmpSearcher.cs

@@ -42,13 +42,13 @@ namespace Mono.Nat
 {
 {
     internal class PmpSearcher : ISearcher
     internal class PmpSearcher : ISearcher
     {
     {
-		static PmpSearcher instance = new PmpSearcher();
-        
-		
-		public static PmpSearcher Instance
-		{
-			get { return instance; }
-		}
+        static PmpSearcher instance = new PmpSearcher();
+
+
+        public static PmpSearcher Instance
+        {
+            get { return instance; }
+        }
 
 
         private int timeout;
         private int timeout;
         private DateTime nextSearch;
         private DateTime nextSearch;
@@ -143,21 +143,21 @@ namespace Mono.Nat
         }
         }
 
 
         public async void Search()
         public async void Search()
-		{
-			foreach (UdpClient s in sockets)
-			{
-				try
-				{
-					await Search(s).ConfigureAwait(false);
-				}
-				catch
-				{
-					// Ignore any search errors
-				}
-			}
-		}
-
-		async Task Search (UdpClient client)
+        {
+            foreach (UdpClient s in sockets)
+            {
+                try
+                {
+                    await Search(s).ConfigureAwait(false);
+                }
+                catch
+                {
+                    // Ignore any search errors
+                }
+            }
+        }
+
+        async Task Search(UdpClient client)
         {
         {
             // Sort out the time for the next search first. The spec says the 
             // Sort out the time for the next search first. The spec says the 
             // timeout should double after each attempt. Once it reaches 64 seconds
             // timeout should double after each attempt. Once it reaches 64 seconds
@@ -175,10 +175,10 @@ namespace Mono.Nat
 
 
             // The nat-pmp search message. Must be sent to GatewayIP:53531
             // The nat-pmp search message. Must be sent to GatewayIP:53531
             byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
             byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
-		    foreach (IPEndPoint gatewayEndpoint in gatewayLists[client])
-		    {
-		        await client.SendAsync(buffer, buffer.Length, gatewayEndpoint).ConfigureAwait(false);
-		    }
+            foreach (IPEndPoint gatewayEndpoint in gatewayLists[client])
+            {
+                await client.SendAsync(buffer, buffer.Length, gatewayEndpoint).ConfigureAwait(false);
+            }
         }
         }
 
 
         bool IsSearchAddress(IPAddress address)
         bool IsSearchAddress(IPAddress address)

+ 4 - 1
Mono.Nat/Upnp/Searchers/UpnpSearcher.cs

@@ -39,6 +39,7 @@ using System.Net.NetworkInformation;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
+using System.Threading.Tasks;
 
 
 namespace Mono.Nat
 namespace Mono.Nat
 {
 {
@@ -61,7 +62,7 @@ namespace Mono.Nat
 		{
 		{
 		}
 		}
 
 
-        public void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint)
+        public async Task Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint)
         {
         {
             // No matter what, this method should never throw an exception. If something goes wrong
             // 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.
             // we should still be in a position to handle the next reply correctly.
@@ -82,6 +83,8 @@ namespace Mono.Nat
                 UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty, _logger, _httpClient);
                 UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty, _logger, _httpClient);
 
 
                 NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
                 NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
+                await d.GetServicesList().ConfigureAwait(false);
+
                 OnDeviceFound(new DeviceEventArgs(d));
                 OnDeviceFound(new DeviceEventArgs(d));
             }
             }
             catch (Exception ex)
             catch (Exception ex)

+ 103 - 2
Mono.Nat/Upnp/UpnpNatDevice.cs

@@ -90,6 +90,104 @@ namespace Mono.Nat.Upnp
             }
             }
         }
         }
 
 
+        public async Task GetServicesList()
+        {
+            // Create a HTTPWebRequest to download the list of services the device offers
+            var message = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger);
+
+            using (var response = await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false))
+            {
+                OnServicesReceived(response);
+            }
+        }
+
+        private void OnServicesReceived(HttpResponseInfo response)
+        {
+            int abortCount = 0;
+            int bytesRead = 0;
+            byte[] buffer = new byte[10240];
+            StringBuilder servicesXml = new StringBuilder();
+            XmlDocument xmldoc = new XmlDocument();
+
+            using (var s = response.Content)
+            {
+                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());
+                        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;
+                        }
+                        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;
+                        }
+                    }
+                }
+
+                //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
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// The EndPoint that the device is at
         /// The EndPoint that the device is at
         /// </summary>
         /// </summary>
@@ -122,10 +220,13 @@ namespace Mono.Nat.Upnp
             get { return serviceType; }
             get { return serviceType; }
         }
         }
 
 
-        public override Task CreatePortMap(Mapping mapping)
+        public override async Task CreatePortMap(Mapping mapping)
         {
         {
             CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
             CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
-            return _httpClient.SendAsync(message.Encode(), message.Method);
+            using (await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false))
+            {
+
+            }
         }
         }
 
 
         public override bool Equals(object obj)
         public override bool Equals(object obj)