diff options
Diffstat (limited to 'Mono.Nat')
| -rw-r--r-- | Mono.Nat/ISearcher.cs | 1 | ||||
| -rw-r--r-- | Mono.Nat/Mono.Nat.csproj | 1 | ||||
| -rw-r--r-- | Mono.Nat/NatUtility.cs | 4 | ||||
| -rw-r--r-- | Mono.Nat/Pmp/Searchers/PmpSearcher.cs | 52 | ||||
| -rw-r--r-- | Mono.Nat/Upnp/Messages/GetServicesMessage.cs | 4 | ||||
| -rw-r--r-- | Mono.Nat/Upnp/Messages/UpnpMessage.cs | 8 | ||||
| -rw-r--r-- | Mono.Nat/Upnp/Searchers/UpnpSearcher.cs | 5 | ||||
| -rw-r--r-- | Mono.Nat/Upnp/Upnp.cs | 95 | ||||
| -rw-r--r-- | Mono.Nat/Upnp/UpnpNatDevice.cs | 126 |
9 files changed, 133 insertions, 163 deletions
diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs index 56e438105..51042bf27 100644 --- a/Mono.Nat/ISearcher.cs +++ b/Mono.Nat/ISearcher.cs @@ -33,6 +33,7 @@ using System.Collections.Generic; using System.Text; using System.Net.Sockets; using System.Net; +using System.Threading.Tasks; namespace Mono.Nat { diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj index abf10f513..426824f0d 100644 --- a/Mono.Nat/Mono.Nat.csproj +++ b/Mono.Nat/Mono.Nat.csproj @@ -67,7 +67,6 @@ <Compile Include="Upnp\Messages\Responses\CreatePortMappingResponseMessage.cs" /> <Compile Include="Upnp\Messages\UpnpMessage.cs" /> <Compile Include="Upnp\Searchers\UpnpSearcher.cs" /> - <Compile Include="Upnp\Upnp.cs" /> <Compile Include="Upnp\UpnpNatDevice.cs" /> </ItemGroup> <ItemGroup> diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs index b9efd9bbd..bf703e399 100644 --- a/Mono.Nat/NatUtility.cs +++ b/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) { case NatProtocol.Upnp: var searcher = new UpnpSearcher(Logger, HttpClient); searcher.DeviceFound += Searcher_DeviceFound; - searcher.Handle(localAddress, deviceInfo, endpoint); + await searcher.Handle(localAddress, deviceInfo, endpoint).ConfigureAwait(false); break; default: throw new ArgumentException("Unexpected protocol: " + protocol); diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs index def50fb72..2836071d0 100644 --- a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs +++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs @@ -42,13 +42,13 @@ namespace Mono.Nat { 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 DateTime nextSearch; @@ -143,21 +143,21 @@ namespace Mono.Nat } 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 // 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 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) diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs index 9d29f98fd..3395b7596 100644 --- a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs +++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs @@ -64,6 +64,10 @@ namespace Mono.Nat.Upnp { var req = new HttpRequestOptions(); + // The periodic request logging may keep some devices awake + req.LogRequestAsDebug = true; + req.LogErrors = false; + req.Url = "http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl; req.RequestHeaders.Add("ACCEPT-LANGUAGE", "en"); diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs index 54cca4494..b0264fc4a 100644 --- a/Mono.Nat/Upnp/Messages/UpnpMessage.cs +++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs @@ -51,9 +51,15 @@ namespace Mono.Nat.Upnp NatUtility.Log("Initiating request to: {0}", ss); var req = new HttpRequestOptions(); + req.LogErrors = false; + + // The periodic request logging may keep some devices awake + req.LogRequestAsDebug = true; + req.Url = ss; req.EnableKeepAlive = false; - req.RequestContentType = "text/xml; charset=\"utf-8\""; + req.RequestContentType = "text/xml"; + req.AppendCharsetToMimeType = true; req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); string bodyString = "<s:Envelope " diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs index 5e36410c5..d9b16dd81 100644 --- a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs +++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs @@ -39,6 +39,7 @@ using System.Net.NetworkInformation; using MediaBrowser.Common.Net; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Dlna; +using System.Threading.Tasks; 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 // 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); NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); + await d.GetServicesList().ConfigureAwait(false); + OnDeviceFound(new DeviceEventArgs(d)); } catch (Exception ex) diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs deleted file mode 100644 index e9321f874..000000000 --- a/Mono.Nat/Upnp/Upnp.cs +++ /dev/null @@ -1,95 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans <ben.motmans@gmail.com> -// Nicholas Terry <nick.i.terry@gmail.com> -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Logging; - -namespace Mono.Nat.Upnp -{ - internal class Upnp - { - protected readonly ILogger Logger; - protected readonly IHttpClient HttpClient; - - public Upnp(ILogger logger, IHttpClient httpClient) - { - Logger = logger; - HttpClient = httpClient; - } - - public virtual Task<UpnpNatDevice> Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) - { - // Convert it to a string for easy parsing - string dataString = null; - - - string urn; - dataString = Encoding.UTF8.GetString(response); - - if (NatUtility.Verbose) - NatUtility.Log("UPnP Response: {0}", dataString); - - /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. - Any other device type is no good to us for this purpose. See the IGP overview paper - page 5 for an overview of device types and their hierarchy. - http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ - - /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which - version it is and apply the correct URN. */ - - /* Some routers don't correctly implement the version ID on the URN, so we only search for the type - prefix. */ - - string log = "UPnP Response: Router advertised a '{0}' service"; - StringComparison c = StringComparison.OrdinalIgnoreCase; - if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) - { - urn = "urn:schemas-upnp-org:service:WANIPConnection:1"; - NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); - } - else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) - { - urn = "urn:schemas-upnp-org:service:WANPPPConnection:1"; - NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); - } - else - throw new NotSupportedException("Received non-supported device type"); - - // We have an internet gateway device now - var device = new UpnpNatDevice(localAddress, dataString, urn, Logger, HttpClient); - return Task.FromResult(device); - } - } -} diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs index ebb1426d1..fda990fa8 100644 --- a/Mono.Nat/Upnp/UpnpNatDevice.cs +++ b/Mono.Nat/Upnp/UpnpNatDevice.cs @@ -90,52 +90,101 @@ namespace Mono.Nat.Upnp } } - internal UpnpNatDevice(IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger, IHttpClient httpClient) + public async Task GetServicesList() { - _logger = logger; - _httpClient = httpClient; - this.LastSeen = DateTime.Now; - this.localAddress = localAddress; + // Create a HTTPWebRequest to download the list of services the device offers + var message = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger); - // 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.OrdinalIgnoreCase) + 9).Split('\r')[0]; - this.serviceType = serviceType; - - // Make sure we have no excess whitespace - locationDetails = locationDetails.Trim(); - - // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address - // Are we going to get addresses with the "http://" attached? - if (locationDetails.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) + using (var response = await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false)) { - NatUtility.Log("Found device at: {0}", locationDetails); - // This bit strings out the "http://" from the string - locationDetails = locationDetails.Substring(7); + OnServicesReceived(response); + } + } - // 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('/')); + private void OnServicesReceived(HttpResponseInfo response) + { + int abortCount = 0; + int bytesRead = 0; + byte[] buffer = new byte[10240]; + StringBuilder servicesXml = new StringBuilder(); + XmlDocument xmldoc = new XmlDocument(); - // From this we parse out the IP address and Port - if (hostAddressAndPort.IndexOf(':') > 0) + using (var s = response.Content) + { + if (response.StatusCode != HttpStatusCode.OK) { - this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))), - Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture)); + NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode); + return; // FIXME: This the best thing to do?? } - else + + while (true) { - // there is no port specified, use default port (80) - this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80); + 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("Parsed device as: {0}", this.hostEndPoint.ToString()); + 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); - // 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); + 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 } } @@ -171,10 +220,13 @@ namespace Mono.Nat.Upnp get { return serviceType; } } - public override Task CreatePortMap(Mapping mapping) + public override async Task CreatePortMap(Mapping mapping) { 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) |
