diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 1430eb9cd6..3d860a27bd 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/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 { - Description = _appHost.Name - }); + await device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) + { + Description = _appHost.Name + }).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error creating port map", ex); + } } // As I said before, this method will be never invoked. You can remove it. diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs index 046cfc10f3..e998a6ea45 100644 --- a/Mono.Nat/AbstractNatDevice.cs +++ b/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) { diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs index c9f27055b8..44ba5223b5 100644 --- a/Mono.Nat/INatDevice.cs +++ b/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; } diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj index 155491c5f7..c31fb3f879 100644 --- a/Mono.Nat/Mono.Nat.csproj +++ b/Mono.Nat/Mono.Nat.csproj @@ -54,7 +54,6 @@ - @@ -80,6 +79,10 @@ + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} MediaBrowser.Controller diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs index b886b87cfd..9c1c4637ab 100644 --- a/Mono.Nat/NatUtility.cs +++ b/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 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); + } + } } } diff --git a/Mono.Nat/Pmp/Mappers/PmpMapper.cs b/Mono.Nat/Pmp/Mappers/PmpMapper.cs index f33ca44c30..9ca2b88569 100644 --- a/Mono.Nat/Pmp/Mappers/PmpMapper.cs +++ b/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 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) diff --git a/Mono.Nat/Pmp/Pmp.cs b/Mono.Nat/Pmp/Pmp.cs deleted file mode 100644 index 6795561b15..0000000000 --- a/Mono.Nat/Pmp/Pmp.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Authors: -// Ben Motmans -// Nicholas Terry -// -// 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 sockets; - protected static Dictionary> gatewayLists; - - internal static void CreateSocketsAndAddGateways() - { - sockets = new List(); - gatewayLists = new Dictionary>(); - - try - { - foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) - { - if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown) - continue; - IPInterfaceProperties properties = n.GetIPProperties(); - List gatewayList = new List(); - - 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. - } - } - } -} diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs index 9a2962c4d5..0337381b6d 100644 --- a/Mono.Nat/Pmp/PmpNatDevice.cs +++ b/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); diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs index df0273ccb8..55605e627e 100644 --- a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs +++ b/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 sockets; + protected static Dictionary> gatewayLists; + + internal static void CreateSocketsAndAddGateways() + { + sockets = new List(); + gatewayLists = new Dictionary>(); + + try + { + foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) + { + if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown) + continue; + IPInterfaceProperties properties = n.GetIPProperties(); + List gatewayList = new List(); + + 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; diff --git a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs index 9169d3f409..ddacb4b117 100644 --- a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs +++ b/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 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 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; } } diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs index ce5270e9b9..f2755c93e9 100644 --- a/Mono.Nat/Upnp/Messages/ErrorMessage.cs +++ b/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) { diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs index 8cc0221be5..87dcb59e4d 100644 --- a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs +++ b/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; + } } } diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs index da650fb418..2d128f8e7e 100644 --- a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs +++ b/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) { diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs index d9be89a693..ac04a66db1 100644 --- a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs +++ b/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); diff --git a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs index 8f97002ea3..b5c9caf9cc 100644 --- a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs +++ b/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) { diff --git a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs index c0c555881b..89980c30c4 100644 --- a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs +++ b/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); diff --git a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs index 314468ece1..3e6bac2a8e 100644 --- a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs +++ b/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()); + } + } } diff --git a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs index e75926b090..b8128f0b54 100644 --- a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs +++ b/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(); diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs index 1fce4eb044..69d32126ad 100644 --- a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs +++ b/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(); diff --git a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs index ee4b18cd10..201296556f 100644 --- a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs +++ b/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(); diff --git a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs index b11bfa0278..51584cc7e3 100644 --- a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs +++ b/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(); diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs index 44c16eec60..11e4462773 100644 --- a/Mono.Nat/Upnp/Messages/UpnpMessage.cs +++ b/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 = "" + + "" + + "" + + methodParameters + + "" + + "" + + "\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); diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs index 3d9df012a1..96bd174eb4 100644 --- a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs +++ b/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)); diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs index 75e2ade8b3..38d949250f 100644 --- a/Mono.Nat/Upnp/Upnp.cs +++ b/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 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); } } } diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs index b7d779994b..6c4bcd7467 100644 --- a/Mono.Nat/Upnp/UpnpNatDevice.cs +++ b/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 - { - private EndPoint hostEndPoint; - private IPAddress localAddress; - private string serviceDescriptionUrl; - private string controlUrl; - private string serviceType; + public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable + { + 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; } - } - - /// - /// The callback to invoke when we are finished setting up the device - /// - 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); - } - } + } + } - /// - /// The EndPoint that the device is at - /// - internal EndPoint HostEndPoint - { - get { return this.hostEndPoint; } - } + /// + /// The EndPoint that the device is at + /// + internal EndPoint HostEndPoint + { + get { return this.hostEndPoint; } + } - /// - /// The relative url of the xml file that describes the list of services is at - /// - internal string ServiceDescriptionUrl - { - get { return this.serviceDescriptionUrl; } - } + /// + /// The relative url of the xml file that describes the list of services is at + /// + internal string ServiceDescriptionUrl + { + get { return this.serviceDescriptionUrl; } + } - /// - /// The relative url that we can use to control the port forwarding - /// - internal string ControlUrl - { - get { return this.controlUrl; } - } + /// + /// The relative url that we can use to control the port forwarding + /// + internal string ControlUrl + { + get { return this.controlUrl; } + } - /// - /// The service type we're using on the device - /// - public string ServiceType - { - get { return serviceType; } - } + /// + /// The service type we're using on the device + /// + public string ServiceType + { + get { return serviceType; } + } - /// - /// Begins an async call to get the external ip address of the router - /// - public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState) - { - // Create the port map message - GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this); - return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal); - } + /// + /// Begins an async call to get the external ip address of the router + /// + public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState) + { + // Create the port map message + GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this); + return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal); + } - /// - /// Maps the specified port to this computer - /// + /// + /// Maps the specified port to this computer + /// public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState) - { + { CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this); return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal); - } + } - /// - /// Removes a port mapping from this computer - /// - public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState) - { - DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this); - return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal); - } + public override Task CreatePortMap(Mapping mapping) + { + CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this); + return _httpClient.SendAsync(message.Encode(), message.Method); + } + + /// + /// Removes a port mapping from this computer + /// + 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 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)); - } + 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)); + } - /// - /// - /// - /// - public override void EndCreatePortMap(IAsyncResult result) - { - if (result == null) throw new ArgumentNullException("result"); + /// + /// + /// + /// + 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"); + 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(); + // 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); - } + // 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; - } + //return result.AsyncState as Mapping; + } - /// - /// - /// - /// - public override void EndDeletePortMap(IAsyncResult result) - { - if (result == null) - throw new ArgumentNullException("result"); + /// + /// + /// + /// + 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"); + 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(); + // 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 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; - } + // If all goes well, we just return + //return true; + } - public override Mapping[] EndGetAllMappings(IAsyncResult result) - { - if (result == null) - throw new ArgumentNullException("result"); + 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"); + GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); - if (!mappingResult.IsCompleted) - mappingResult.AsyncWaitHandle.WaitOne(); + 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); - } + 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(); - } + return mappingResult.Mappings.ToArray(); + } - /// - /// Ends an async request to get the external ip address of the router - /// - public override IPAddress EndGetExternalIP(IAsyncResult result) - { - if (result == null) throw new ArgumentNullException("result"); + /// + /// Ends an async request to get the external ip address of the router + /// + 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"); + PortMapAsyncResult mappingResult = result as PortMapAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); - if (!result.IsCompleted) - result.AsyncWaitHandle.WaitOne(); + 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 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; - } + 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"); + 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"); + GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); - if (!mappingResult.IsCompleted) - mappingResult.AsyncWaitHandle.WaitOne(); + 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); + 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]; - } + return mappingResult.Mappings[0]; + } - public override bool Equals(object obj) - { - UpnpNatDevice device = obj as UpnpNatDevice; - return (device == null) ? false : this.Equals((device)); - } + 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 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()); - } + 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); + 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; - } + 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; + 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]; + 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)); - } + // 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()); - } + // 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 EndCreatePortMapInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } - private void EndMessageInternal(IAsyncResult result) - { - HttpWebResponse response = null; - PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult; + 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); - } + 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(); - } - } + finally + { + if (response != null) + response.Close(); + } + } - private void EndDeletePortMapInternal(IAsyncResult result) - { - EndMessageInternal(result); - CompleteMessage(result); - } + private void EndDeletePortMapInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } - private void EndGetAllMappingsInternal(IAsyncResult result) - { - EndMessageInternal(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); + 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; - } + // 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); - } + CompleteMessage(result); + } - private void EndGetExternalIPInternal(IAsyncResult result) - { - EndMessageInternal(result); - CompleteMessage(result); - } + private void EndGetExternalIPInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } - private void EndGetSpecificMappingInternal(IAsyncResult result) - { - EndMessageInternal(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); - } + 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); - } + 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; + internal async Task GetServicesList() + { + // Create a HTTPWebRequest to download the list of services the device offers + var requestOptions = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger).Encode(); - // 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); - } + requestOptions.BufferContent = false; - 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(); + using (var response = await _httpClient.Get(requestOptions).ConfigureAwait(false)) + { + return ServicesReceived(response); + } + } - 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?? - } + 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()); - 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); - } - } + 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); + 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; - } - } - } + 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 - } - 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(); - } - } + //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; + } /// /// Overridden. /// /// - 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); } - } + } } \ No newline at end of file