diff options
Diffstat (limited to 'Mono.Nat')
31 files changed, 486 insertions, 2281 deletions
diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs index 046cfc10f..1b4216002 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,48 +51,6 @@ namespace Mono.Nat set { lastSeen = value; } } - public virtual void CreatePortMap (Mapping mapping) - { - IAsyncResult result = BeginCreatePortMap (mapping, null, null); - EndCreatePortMap(result); - } - - public virtual void DeletePortMap (Mapping mapping) - { - IAsyncResult result = BeginDeletePortMap (mapping, null, mapping); - EndDeletePortMap(result); - } - - public virtual Mapping[] GetAllMappings () - { - IAsyncResult result = BeginGetAllMappings (null, null); - return EndGetAllMappings (result); - } - - public virtual IPAddress GetExternalIP () - { - IAsyncResult result = BeginGetExternalIP(null, null); - return EndGetExternalIP(result); - } - - public virtual Mapping GetSpecificMapping (Protocol protocol, int port) - { - IAsyncResult result = this.BeginGetSpecificMapping (protocol, port, null, null); - return this.EndGetSpecificMapping(result); - } - - public abstract IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState); - public abstract IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState); - - public abstract IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState); - public abstract IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState); - public abstract IAsyncResult BeginGetSpecificMapping(Protocol protocol, int externalPort, AsyncCallback callback, object asyncState); - - public abstract void EndCreatePortMap (IAsyncResult result); - public abstract void EndDeletePortMap (IAsyncResult result); - - public abstract Mapping[] EndGetAllMappings (IAsyncResult result); - public abstract IPAddress EndGetExternalIP (IAsyncResult result); - public abstract Mapping EndGetSpecificMapping (IAsyncResult result); + public abstract Task CreatePortMap(Mapping mapping); } } diff --git a/Mono.Nat/Exceptions/MappingException.cs b/Mono.Nat/Exceptions/MappingException.cs index bb2e6a69d..9c0c4f122 100644 --- a/Mono.Nat/Exceptions/MappingException.cs +++ b/Mono.Nat/Exceptions/MappingException.cs @@ -25,11 +25,9 @@ // using System; -using System.Security.Permissions; namespace Mono.Nat { - [Serializable] public class MappingException : Exception { private int errorCode; @@ -45,7 +43,6 @@ namespace Mono.Nat get { return this.errorText; } } - #region Constructors public MappingException() : base() { @@ -67,21 +64,5 @@ namespace Mono.Nat : base(message, innerException) { } - - protected MappingException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) - : base(info, context) - { - } - #endregion - - [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)] - public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) - { - if(info==null) throw new ArgumentNullException("info"); - - this.errorCode = info.GetInt32("errorCode"); - this.errorText = info.GetString("errorText"); - base.GetObjectData(info, context); - } } } diff --git a/Mono.Nat/IMapper.cs b/Mono.Nat/IMapper.cs deleted file mode 100644 index b18e6cff2..000000000 --- a/Mono.Nat/IMapper.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Authors: -// Nicholas Terry <nick.i.terry@gmail.com> -// -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; - -namespace Mono.Nat -{ - public enum MapperType - { - Pmp, - Upnp - } - - internal interface IMapper - { - event EventHandler<DeviceEventArgs> DeviceFound; - - void Map(IPAddress gatewayAddress); - - void Handle(IPAddress localAddres, byte[] response); - } -} diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs index c9f27055b..b0401627b 100644 --- a/Mono.Nat/INatDevice.cs +++ b/Mono.Nat/INatDevice.cs @@ -30,32 +30,15 @@ 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); - void DeletePortMap (Mapping mapping); + Task CreatePortMap (Mapping mapping); IPAddress LocalAddress { get; } - Mapping[] GetAllMappings (); - IPAddress GetExternalIP (); - Mapping GetSpecificMapping (Protocol protocol, int port); - - IAsyncResult BeginCreatePortMap (Mapping mapping, AsyncCallback callback, object asyncState); - IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState); - - IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState); - IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState); - IAsyncResult BeginGetSpecificMapping (Protocol protocol, int externalPort, AsyncCallback callback, object asyncState); - - void EndCreatePortMap (IAsyncResult result); - void EndDeletePortMap (IAsyncResult result); - - Mapping[] EndGetAllMappings (IAsyncResult result); - IPAddress EndGetExternalIP (IAsyncResult result); - Mapping EndGetSpecificMapping (IAsyncResult result); DateTime LastSeen { get; set; } } diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj deleted file mode 100644 index 9c2781433..000000000 --- a/Mono.Nat/Mono.Nat.csproj +++ /dev/null @@ -1,104 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{D7453B88-2266-4805-B39B-2B5A2A33E1BA}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>Mono.Nat</RootNamespace> - <AssemblyName>Mono.Nat</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <FileAlignment>512</FileAlignment> - <TargetFrameworkProfile /> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>pdbonly</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <ItemGroup> - <Reference Include="System" /> - <Reference Include="System.Core" /> - <Reference Include="System.Xml.Linq" /> - <Reference Include="System.Data.DataSetExtensions" /> - <Reference Include="Microsoft.CSharp" /> - <Reference Include="System.Data" /> - <Reference Include="System.Net.Http" /> - <Reference Include="System.Xml" /> - </ItemGroup> - <ItemGroup> - <Compile Include="..\SharedVersion.cs"> - <Link>Properties\SharedVersion.cs</Link> - </Compile> - <Compile Include="AbstractNatDevice.cs" /> - <Compile Include="AsyncResults\AsyncResult.cs" /> - <Compile Include="Enums\MapState.cs" /> - <Compile Include="Enums\ProtocolType.cs" /> - <Compile Include="EventArgs\DeviceEventArgs.cs" /> - <Compile Include="Exceptions\MappingException.cs" /> - <Compile Include="IMapper.cs" /> - <Compile Include="INatDevice.cs" /> - <Compile Include="ISearcher.cs" /> - <Compile Include="Mapping.cs" /> - <Compile Include="NatProtocol.cs" /> - <Compile Include="NatUtility.cs" /> - <Compile Include="Pmp\AsyncResults\PortMapAsyncResult.cs" /> - <Compile Include="Pmp\Mappers\PmpMapper.cs" /> - <Compile Include="Pmp\Pmp.cs" /> - <Compile Include="Pmp\PmpConstants.cs" /> - <Compile Include="Pmp\PmpNatDevice.cs" /> - <Compile Include="Pmp\Searchers\PmpSearcher.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Upnp\AsyncResults\GetAllMappingsAsyncResult.cs" /> - <Compile Include="Upnp\AsyncResults\PortMapAsyncResult.cs" /> - <Compile Include="Upnp\Mappers\UpnpMapper.cs" /> - <Compile Include="Upnp\Messages\DiscoverDeviceMessage.cs" /> - <Compile Include="Upnp\Messages\ErrorMessage.cs" /> - <Compile Include="Upnp\Messages\GetServicesMessage.cs" /> - <Compile Include="Upnp\Messages\Requests\CreatePortMappingMessage.cs" /> - <Compile Include="Upnp\Messages\Requests\DeletePortMappingMessage.cs" /> - <Compile Include="Upnp\Messages\Requests\GetExternalIPAddressMessage.cs" /> - <Compile Include="Upnp\Messages\Requests\GetGenericPortMappingEntry.cs" /> - <Compile Include="Upnp\Messages\Requests\GetSpecificPortMappingEntryMessage.cs" /> - <Compile Include="Upnp\Messages\Responses\CreatePortMappingResponseMessage.cs" /> - <Compile Include="Upnp\Messages\Responses\DeletePortMappingResponseMessage.cs" /> - <Compile Include="Upnp\Messages\Responses\GetExternalIPAddressResponseMessage.cs" /> - <Compile Include="Upnp\Messages\Responses\GetGenericPortMappingEntryResponseMessage.cs" /> - <Compile Include="Upnp\Messages\UpnpMessage.cs" /> - <Compile Include="Upnp\Searchers\UpnpSearcher.cs" /> - <Compile Include="Upnp\Upnp.cs" /> - <Compile Include="Upnp\UpnpNatDevice.cs" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> - <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> - <Name>MediaBrowser.Controller</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> - <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> - <Name>MediaBrowser.Model</Name> - </ProjectReference> - </ItemGroup> - <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Microsoft.Common.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> -</Project>
\ No newline at end of file diff --git a/Mono.Nat/Mono.Nat.xproj b/Mono.Nat/Mono.Nat.xproj new file mode 100644 index 000000000..3479a2a67 --- /dev/null +++ b/Mono.Nat/Mono.Nat.xproj @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + </PropertyGroup> + <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> + <PropertyGroup Label="Globals"> + <ProjectGuid>4acab6a2-ac9a-4b50-baec-1fe4a1f3b8bc</ProjectGuid> + <RootNamespace>Mono.Nat</RootNamespace> + <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> + <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> + <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + </PropertyGroup> + <ItemGroup> + <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> + <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> + </ItemGroup> + <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> +</Project>
\ No newline at end of file diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs index 6d91d2513..bcbe5d8d0 100644 --- a/Mono.Nat/NatUtility.cs +++ b/Mono.Nat/NatUtility.cs @@ -34,10 +34,10 @@ using System.Linq; using System.Collections.Generic; using System.IO; using System.Net.NetworkInformation; -using MediaBrowser.Controller.Dlna; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Logging; -using Mono.Nat.Pmp.Mappers; -using Mono.Nat.Upnp.Mappers; namespace Mono.Nat { @@ -47,16 +47,15 @@ namespace Mono.Nat public static event EventHandler<DeviceEventArgs> DeviceFound; public static event EventHandler<DeviceEventArgs> DeviceLost; - public static event EventHandler<UnhandledExceptionEventArgs> UnhandledException; - private static List<ISearcher> controllers; private static bool verbose; public static List<NatProtocol> EnabledProtocols { get; set; } public static ILogger Logger { get; set; } + public static IHttpClient HttpClient { get; set; } - public static bool Verbose + public static bool Verbose { get { return verbose; } set { verbose = value; } @@ -66,14 +65,12 @@ namespace Mono.Nat { EnabledProtocols = new List<NatProtocol> { - NatProtocol.Upnp, NatProtocol.Pmp }; searching = new ManualResetEvent(false); controllers = new List<ISearcher>(); - controllers.Add(UpnpSearcher.Instance); controllers.Add(PmpSearcher.Instance); controllers.ForEach(searcher => @@ -89,9 +86,8 @@ namespace Mono.Nat DeviceLost(sender, args); }; }); - Thread t = new Thread(SearchAndListen); - t.IsBackground = true; - t.Start(); + + Task.Factory.StartNew(SearchAndListen, TaskCreationOptions.LongRunning); } internal static void Log(string format, params object[] args) @@ -101,7 +97,7 @@ namespace Mono.Nat logger.Debug(format, args); } - private static void SearchAndListen() + private static async Task SearchAndListen() { while (true) { @@ -111,58 +107,42 @@ namespace Mono.Nat { var enabledProtocols = EnabledProtocols.ToList(); - if (enabledProtocols.Contains(UpnpSearcher.Instance.Protocol)) - { - Receive(UpnpSearcher.Instance, UpnpSearcher.sockets); - } if (enabledProtocols.Contains(PmpSearcher.Instance.Protocol)) { - Receive(PmpSearcher.Instance, PmpSearcher.sockets); + await Receive(PmpSearcher.Instance, PmpSearcher.sockets).ConfigureAwait(false); } foreach (ISearcher s in controllers) + { if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol)) { Log("Searching for: {0}", s.GetType().Name); - s.Search(); + s.Search(); } + } } catch (Exception e) { - if (UnhandledException != null) - UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false)); + } - Thread.Sleep(10); + await Task.Delay(100).ConfigureAwait(false); } } - static void Receive (ISearcher searcher, List<UdpClient> clients) + static async Task Receive (ISearcher searcher, List<UdpClient> clients) { - IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); foreach (UdpClient client in clients) { if (client.Available > 0) { IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address; - byte[] data = client.Receive(ref received); + var result = await client.ReceiveAsync().ConfigureAwait(false); + var data = result.Buffer; + var received = result.RemoteEndPoint; searcher.Handle(localAddress, data, received); } } } - - static void Receive(IMapper mapper, List<UdpClient> clients) - { - IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); - foreach (UdpClient client in clients) - { - if (client.Available > 0) - { - IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address; - byte[] data = client.Receive(ref received); - mapper.Handle(localAddress, data); - } - } - } public static void StartDiscovery () { @@ -173,49 +153,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(); - mapper.DeviceFound += (sender, args) => - { - if (DeviceFound != null) - DeviceFound(sender, args); - }; - mapper.Map(gatewayAddress); - break; - default: - throw new InvalidOperationException("Unsuported type given"); - - } - searching.Reset(); - - } - - //So then why is it here? -Nick - [Obsolete ("This method serves no purpose and shouldn't be used")] - public static IPAddress[] GetLocalAddresses (bool includeIPv6) - { - List<IPAddress> addresses = new List<IPAddress> (); - - IPHostEntry hostInfo = Dns.GetHostEntry (Dns.GetHostName ()); - foreach (IPAddress address in hostInfo.AddressList) { - if (address.AddressFamily == AddressFamily.InterNetwork || - (includeIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) { - addresses.Add (address); - } - } - - return addresses.ToArray (); - } //checks if an IP address is a private address space as defined by RFC 1918 public static bool IsPrivateAddressSpace (IPAddress address) @@ -239,7 +176,7 @@ namespace Mono.Nat switch (protocol) { case NatProtocol.Upnp: - UpnpSearcher.Instance.Handle(localAddress, response, endpoint); + //UpnpSearcher.Instance.Handle(localAddress, response, endpoint); break; case NatProtocol.Pmp: PmpSearcher.Instance.Handle(localAddress, response, endpoint); @@ -254,11 +191,21 @@ namespace Mono.Nat switch (protocol) { case NatProtocol.Upnp: - UpnpSearcher.Instance.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 deleted file mode 100644 index f33ca44c3..000000000 --- a/Mono.Nat/Pmp/Mappers/PmpMapper.cs +++ /dev/null @@ -1,83 +0,0 @@ -// -// Authors: -// Nicholas Terry <nick.i.terry@gmail.com> -// -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using Mono.Nat.Pmp; - -namespace Mono.Nat.Pmp.Mappers -{ - internal class PmpMapper : Pmp, IMapper - { - public event EventHandler<DeviceEventArgs> DeviceFound; - - static PmpMapper() - { - CreateSocketsAndAddGateways(); - } - - public void Map(IPAddress gatewayAddress) - { - sockets.ForEach(x => Map(x, gatewayAddress)); - } - - void Map(UdpClient client, IPAddress gatewayAddress) - { - // The nat-pmp search message. Must be sent to GatewayIP:53531 - byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode }; - - client.Send(buffer, buffer.Length, new IPEndPoint(gatewayAddress, PmpConstants.ServerPort)); - } - - public void Handle(IPAddress localAddres, byte[] response) - { - //if (!IsSearchAddress(endpoint.Address)) - // return; - if (response.Length != 12) - return; - if (response[0] != PmpConstants.Version) - return; - if (response[1] != PmpConstants.ServerNoop) - return; - int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); - if (errorcode != 0) - NatUtility.Log("Non zero error: {0}", errorcode); - - IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] }); - OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(localAddres, publicIp))); - } - - private void OnDeviceFound(DeviceEventArgs args) - { - if (DeviceFound != null) - DeviceFound(this, args); - } - } -} diff --git a/Mono.Nat/Pmp/Pmp.cs b/Mono.Nat/Pmp/Pmp.cs deleted file mode 100644 index 6795561b1..000000000 --- a/Mono.Nat/Pmp/Pmp.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Authors: -// Ben Motmans <ben.motmans@gmail.com> -// Nicholas Terry <nick.i.terry@gmail.com> -// -// Copyright (C) 2007 Ben Motmans -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Text; - -namespace Mono.Nat.Pmp -{ - internal abstract class Pmp - { - public static List<UdpClient> sockets; - protected static Dictionary<UdpClient, List<IPEndPoint>> gatewayLists; - - internal static void CreateSocketsAndAddGateways() - { - sockets = new List<UdpClient>(); - gatewayLists = new Dictionary<UdpClient, List<IPEndPoint>>(); - - try - { - foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) - { - if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown) - continue; - IPInterfaceProperties properties = n.GetIPProperties(); - List<IPEndPoint> gatewayList = new List<IPEndPoint>(); - - foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses) - { - if (gateway.Address.AddressFamily == AddressFamily.InterNetwork) - { - gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort)); - } - } - if (gatewayList.Count == 0) - { - /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */ - foreach (var gw2 in properties.DnsAddresses) - { - if (gw2.AddressFamily == AddressFamily.InterNetwork) - { - gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort)); - } - } - foreach (var unicast in properties.UnicastAddresses) - { - if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred - && unicast.AddressPreferredLifetime != UInt32.MaxValue - && */unicast.Address.AddressFamily == AddressFamily.InterNetwork) - { - var bytes = unicast.Address.GetAddressBytes(); - bytes[3] = 1; - gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort)); - } - } - } - - if (gatewayList.Count > 0) - { - foreach (UnicastIPAddressInformation address in properties.UnicastAddresses) - { - if (address.Address.AddressFamily == AddressFamily.InterNetwork) - { - UdpClient client; - - try - { - client = new UdpClient(new IPEndPoint(address.Address, 0)); - } - catch (SocketException) - { - continue; // Move on to the next address. - } - - gatewayLists.Add(client, gatewayList); - sockets.Add(client); - } - } - } - } - } - catch (Exception) - { - // NAT-PMP does not use multicast, so there isn't really a good fallback. - } - } - } -} diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs index 9a2962c4d..93007cb8a 100644 --- a/Mono.Nat/Pmp/PmpNatDevice.cs +++ b/Mono.Nat/Pmp/PmpNatDevice.cs @@ -30,318 +30,173 @@ using System.Net; using System.Net.Sockets; using System.Threading; using System.Collections.Generic; +using System.Threading.Tasks; namespace Mono.Nat.Pmp { - internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice> - { - private AsyncResult externalIpResult; - private bool pendingOp; - private IPAddress localAddress; - private IPAddress publicAddress; - - internal PmpNatDevice (IPAddress localAddress, IPAddress publicAddress) - { - this.localAddress = localAddress; - this.publicAddress = publicAddress; - } - - public override IPAddress LocalAddress - { - get { return localAddress; } - } + internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice> + { + private IPAddress localAddress; + private IPAddress publicAddress; - public override IPAddress GetExternalIP () - { - return publicAddress; - } - - public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState) - { - PortMapAsyncResult pmar = new PortMapAsyncResult (mapping.Protocol, mapping.PublicPort, PmpConstants.DefaultLeaseTime, callback, asyncState); - ThreadPool.QueueUserWorkItem (delegate - { - try - { - CreatePortMap(pmar.Mapping, true); - pmar.Complete(); - } - catch (Exception e) - { - pmar.Complete(e); - } - }); - return pmar; - } - - public override IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState) - { - PortMapAsyncResult pmar = new PortMapAsyncResult (mapping, callback, asyncState); - ThreadPool.QueueUserWorkItem (delegate { - try { - CreatePortMap(pmar.Mapping, false); - pmar.Complete(); - } catch (Exception e) { - pmar.Complete(e); - } - }); - return pmar; - } - - public override void EndCreatePortMap (IAsyncResult result) - { - PortMapAsyncResult pmar = result as PortMapAsyncResult; - pmar.AsyncWaitHandle.WaitOne (); - } - - public override void EndDeletePortMap (IAsyncResult result) - { - PortMapAsyncResult pmar = result as PortMapAsyncResult; - pmar.AsyncWaitHandle.WaitOne (); - } - - public override IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState) - { - //NAT-PMP does not specify a way to get all port mappings - throw new NotSupportedException (); - } + internal PmpNatDevice(IPAddress localAddress, IPAddress publicAddress) + { + this.localAddress = localAddress; + this.publicAddress = publicAddress; + } - public override IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState) - { - StartOp(ref externalIpResult, callback, asyncState); - AsyncResult result = externalIpResult; - result.Complete(); - return result; - } + public override IPAddress LocalAddress + { + get { return localAddress; } + } - public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState) - { - //NAT-PMP does not specify a way to get a specific port map - throw new NotSupportedException (); - } - - public override Mapping[] EndGetAllMappings (IAsyncResult result) - { - //NAT-PMP does not specify a way to get all port mappings - throw new NotSupportedException (); - } + public override Task CreatePortMap(Mapping mapping) + { + return InternalCreatePortMapAsync(mapping, true); + } - public override IPAddress EndGetExternalIP (IAsyncResult result) - { - EndOp(result, ref externalIpResult); - return publicAddress; - } + public override bool Equals(object obj) + { + PmpNatDevice device = obj as PmpNatDevice; + return (device == null) ? false : this.Equals(device); + } - private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState) + public override int GetHashCode() { - if (pendingOp == true) - throw new InvalidOperationException("Can only have one simultaenous async operation"); + return this.publicAddress.GetHashCode(); + } - pendingOp = true; - result = new AsyncResult(callback, asyncState); + public bool Equals(PmpNatDevice other) + { + return (other == null) ? false : this.publicAddress.Equals(other.publicAddress); } - private void EndOp(IAsyncResult supplied, ref AsyncResult actual) + private async Task<Mapping> InternalCreatePortMapAsync(Mapping mapping, bool create) { - if (supplied == null) - throw new ArgumentNullException("result"); + var package = new List<byte>(); + + package.Add(PmpConstants.Version); + package.Add(mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp); + package.Add(0); //reserved + package.Add(0); //reserved + package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)mapping.PrivatePort))); + package.AddRange( + BitConverter.GetBytes(create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0)); + package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(mapping.Lifetime))); + + try + { + byte[] buffer = package.ToArray(); + int attempt = 0; + int delay = PmpConstants.RetryDelay; - if (supplied != actual) - throw new ArgumentException("Supplied IAsyncResult does not match the stored result"); + using (var udpClient = new UdpClient()) + { + var cancellationTokenSource = new CancellationTokenSource(); - if (!supplied.IsCompleted) - supplied.AsyncWaitHandle.WaitOne(); + while (attempt < PmpConstants.RetryAttempts) + { + await udpClient.SendAsync(buffer, buffer.Length, + new IPEndPoint(LocalAddress, PmpConstants.ServerPort)); - if (actual.StoredException != null) - throw actual.StoredException; + if (attempt == 0) + { + Task.Run(() => CreatePortMapListen(udpClient, mapping, cancellationTokenSource.Token)); + } - pendingOp = false; - actual = null; - } + attempt++; + delay *= 2; + await Task.Delay(delay).ConfigureAwait(false); + } + + cancellationTokenSource.Cancel(); + } + } + catch (OperationCanceledException) + { - public override Mapping EndGetSpecificMapping (IAsyncResult result) - { - //NAT-PMP does not specify a way to get a specific port map - throw new NotSupportedException (); - } - - public override bool Equals(object obj) - { - PmpNatDevice device = obj as PmpNatDevice; - return (device == null) ? false : this.Equals(device); - } - - public override int GetHashCode () - { - return this.publicAddress.GetHashCode(); - } + } + catch (Exception e) + { + string type = create ? "create" : "delete"; + string message = String.Format("Failed to {0} portmap (protocol={1}, private port={2}) {3}", + type, + mapping.Protocol, + mapping.PrivatePort, + e.Message); + NatUtility.Log(message); + var pmpException = e as MappingException; + throw new MappingException(message, pmpException); + } + + return mapping; + } - public bool Equals (PmpNatDevice other) - { - return (other == null) ? false : this.publicAddress.Equals(other.publicAddress); - } + private async void CreatePortMapListen(UdpClient udpClient, Mapping mapping, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + var result = await udpClient.ReceiveAsync().ConfigureAwait(false); + var endPoint = result.RemoteEndPoint; + byte[] data = data = result.Buffer; - private Mapping CreatePortMap (Mapping mapping, bool create) - { - List<byte> package = new List<byte> (); - - package.Add (PmpConstants.Version); - package.Add (mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp); - package.Add ((byte)0); //reserved - package.Add ((byte)0); //reserved - package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder((short)mapping.PrivatePort))); - package.AddRange (BitConverter.GetBytes (create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0)); - package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder(mapping.Lifetime))); + if (data.Length < 16) + continue; - CreatePortMapAsyncState state = new CreatePortMapAsyncState (); - state.Buffer = package.ToArray (); - state.Mapping = mapping; + if (data[0] != PmpConstants.Version) + continue; - ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapAsync), state); - WaitHandle.WaitAll (new WaitHandle[] {state.ResetEvent}); - - if (!state.Success) { - string type = create ? "create" : "delete"; - throw new MappingException (String.Format ("Failed to {0} portmap (protocol={1}, private port={2}", type, mapping.Protocol, mapping.PrivatePort)); - } - - return state.Mapping; - } - - private void CreatePortMapAsync (object obj) - { - CreatePortMapAsyncState state = obj as CreatePortMapAsyncState; - - UdpClient udpClient = new UdpClient (); - CreatePortMapListenState listenState = new CreatePortMapListenState (state, udpClient); + var opCode = (byte)(data[1] & 127); - int attempt = 0; - int delay = PmpConstants.RetryDelay; - - ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState); + var protocol = Protocol.Tcp; + if (opCode == PmpConstants.OperationCodeUdp) + protocol = Protocol.Udp; - while (attempt < PmpConstants.RetryAttempts && !listenState.Success) { - udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort)); - listenState.UdpClientReady.Set(); + short resultCode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 2)); + int epoch = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 4)); - attempt++; - delay *= 2; - Thread.Sleep (delay); - } - - state.Success = listenState.Success; - - udpClient.Close (); - state.ResetEvent.Set (); - } - - private void CreatePortMapListen (object obj) - { - CreatePortMapListenState state = obj as CreatePortMapListenState; + short privatePort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 8)); + short publicPort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10)); - UdpClient udpClient = state.UdpClient; - state.UdpClientReady.WaitOne(); // Evidently UdpClient has some lazy-init Send/Receive race? - IPEndPoint endPoint = new IPEndPoint (localAddress, PmpConstants.ServerPort); - - while (!state.Success) - { - byte[] data; - try - { - data = udpClient.Receive(ref endPoint); - } - catch (SocketException) - { - state.Success = false; - return; - } + var lifetime = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 12)); - catch (ObjectDisposedException) + if (privatePort < 0 || publicPort < 0 || resultCode != PmpConstants.ResultCodeSuccess) { - state.Success = false; + var errors = new[] + { + "Success", + "Unsupported Version", + "Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off)" + , + "Network Failure (e.g. NAT box itself has not obtained a DHCP lease)", + "Out of resources (NAT box cannot create any more mappings at this time)", + "Unsupported opcode" + }; + + var errorMsg = errors[resultCode]; + NatUtility.Log("Error in CreatePortMapListen: " + errorMsg); return; } - - if (data.Length < 16) - continue; - if (data[0] != PmpConstants.Version) - continue; - - byte opCode = (byte)(data[1] & (byte)127); - - Protocol protocol = Protocol.Tcp; - if (opCode == PmpConstants.OperationCodeUdp) - protocol = Protocol.Udp; - - short resultCode = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 2)); - uint epoch = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 4)); - - int privatePort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 8)); - int publicPort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 10)); - - uint lifetime = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 12)); - - if (publicPort < 0 || privatePort < 0 || resultCode != PmpConstants.ResultCodeSuccess) - { - state.Success = false; - return; - } - - if (lifetime == 0) - { - //mapping was deleted - state.Success = true; - state.Mapping = null; - return; - } - else - { - //mapping was created - //TODO: verify that the private port+protocol are a match - Mapping mapping = state.Mapping; - mapping.PublicPort = publicPort; - mapping.Protocol = protocol; - mapping.Expiration = DateTime.Now.AddSeconds (lifetime); - - state.Success = true; - } - } - } + if (lifetime == 0) return; //mapping was deleted + //mapping was created + //TODO: verify that the private port+protocol are a match + mapping.PublicPort = publicPort; + mapping.Protocol = protocol; + mapping.Expiration = DateTime.Now.AddSeconds(lifetime); + return; + } + } /// <summary> /// Overridden. /// </summary> /// <returns></returns> - public override string ToString( ) + public override string ToString() { - return String.Format( "PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}", - this.localAddress, this.publicAddress, this.LastSeen ); + return String.Format("PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}", + this.localAddress, this.publicAddress, this.LastSeen); } - - - private class CreatePortMapAsyncState - { - internal byte[] Buffer; - internal ManualResetEvent ResetEvent = new ManualResetEvent (false); - internal Mapping Mapping; - - internal bool Success; - } - - private class CreatePortMapListenState - { - internal volatile bool Success; - internal Mapping Mapping; - internal UdpClient UdpClient; - internal ManualResetEvent UdpClientReady; - - internal CreatePortMapListenState (CreatePortMapAsyncState state, UdpClient client) - { - Mapping = state.Mapping; - UdpClient = client; UdpClientReady = new ManualResetEvent(false); - } - } - } + } }
\ No newline at end of file diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs index df0273ccb..4a8a90412 100644 --- a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs +++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs @@ -37,10 +37,11 @@ using Mono.Nat.Pmp; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Linq; +using System.Threading.Tasks; namespace Mono.Nat { - internal class PmpSearcher : Pmp.Pmp, ISearcher + internal class PmpSearcher : ISearcher { static PmpSearcher instance = new PmpSearcher(); @@ -60,18 +61,95 @@ namespace Mono.Nat CreateSocketsAndAddGateways(); } + public static List<UdpClient> sockets; + protected static Dictionary<UdpClient, List<IPEndPoint>> gatewayLists; + + internal static void CreateSocketsAndAddGateways() + { + sockets = new List<UdpClient>(); + gatewayLists = new Dictionary<UdpClient, List<IPEndPoint>>(); + + try + { + foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) + { + if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown) + continue; + IPInterfaceProperties properties = n.GetIPProperties(); + List<IPEndPoint> gatewayList = new List<IPEndPoint>(); + + foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses) + { + if (gateway.Address.AddressFamily == AddressFamily.InterNetwork) + { + gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort)); + } + } + if (gatewayList.Count == 0) + { + /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */ + foreach (var gw2 in properties.DnsAddresses) + { + if (gw2.AddressFamily == AddressFamily.InterNetwork) + { + gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort)); + } + } + foreach (var unicast in properties.UnicastAddresses) + { + if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred + && unicast.AddressPreferredLifetime != UInt32.MaxValue + && */unicast.Address.AddressFamily == AddressFamily.InterNetwork) + { + var bytes = unicast.Address.GetAddressBytes(); + bytes[3] = 1; + gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort)); + } + } + } + + if (gatewayList.Count > 0) + { + foreach (UnicastIPAddressInformation address in properties.UnicastAddresses) + { + if (address.Address.AddressFamily == AddressFamily.InterNetwork) + { + UdpClient client; + + try + { + client = new UdpClient(new IPEndPoint(address.Address, 0)); + } + catch (SocketException) + { + continue; // Move on to the next address. + } + + gatewayLists.Add(client, gatewayList); + sockets.Add(client); + } + } + } + } + } + catch (Exception) + { + // NAT-PMP does not use multicast, so there isn't really a good fallback. + } + } + PmpSearcher() { timeout = 250; } - public void Search() + public async void Search() { foreach (UdpClient s in sockets) { try { - Search(s); + await Search(s).ConfigureAwait(false); } catch { @@ -80,7 +158,7 @@ namespace Mono.Nat } } - void Search (UdpClient client) + 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 @@ -98,8 +176,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]) - client.Send(buffer, buffer.Length, gatewayEndpoint); + foreach (IPEndPoint gatewayEndpoint in gatewayLists[client]) + { + await client.SendAsync(buffer, buffer.Length, gatewayEndpoint).ConfigureAwait(false); + } } bool IsSearchAddress(IPAddress address) diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs index c3c3101de..2a4e75c21 100644 --- a/Mono.Nat/Properties/AssemblyInfo.cs +++ b/Mono.Nat/Properties/AssemblyInfo.cs @@ -2,30 +2,15 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Mono.Nat")] -[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Mono.Nat")] -[assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d7453b88-2266-4805-b39b-2b5a2a33e1ba")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -//
\ No newline at end of file diff --git a/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs deleted file mode 100644 index 51ecfbaf0..000000000 --- a/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; - -namespace Mono.Nat.Upnp -{ - internal class GetAllMappingsAsyncResult : PortMapAsyncResult - { - private List<Mapping> mappings; - private Mapping specificMapping; - - public GetAllMappingsAsyncResult(WebRequest request, AsyncCallback callback, object asyncState) - : base(request, callback, asyncState) - { - mappings = new List<Mapping>(); - } - - public List<Mapping> Mappings - { - get { return this.mappings; } - } - - public Mapping SpecificMapping - { - get { return this.specificMapping; } - set { this.specificMapping = value; } - } - } -} diff --git a/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs deleted file mode 100644 index d8ac3fe61..000000000 --- a/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs +++ /dev/null @@ -1,75 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - - - -using System; -using System.Net; -using System.Threading; - -namespace Mono.Nat.Upnp -{ - internal class PortMapAsyncResult : AsyncResult - { - private WebRequest request; - private MessageBase savedMessage; - - protected PortMapAsyncResult(WebRequest request, AsyncCallback callback, object asyncState) - : base (callback, asyncState) - { - this.request = request; - } - - internal WebRequest Request - { - get { return this.request; } - set { this.request = value; } - } - - internal MessageBase SavedMessage - { - get { return this.savedMessage; } - set { this.savedMessage = value; } - } - - internal static PortMapAsyncResult Create (MessageBase message, WebRequest request, AsyncCallback storedCallback, object asyncState) - { - if (message is GetGenericPortMappingEntry) - return new GetAllMappingsAsyncResult(request, storedCallback, asyncState); - - if (message is GetSpecificPortMappingEntryMessage) - { - GetSpecificPortMappingEntryMessage mapMessage = (GetSpecificPortMappingEntryMessage)message; - GetAllMappingsAsyncResult result = new GetAllMappingsAsyncResult(request, storedCallback, asyncState); - - result.SpecificMapping = new Mapping(mapMessage.protocol, 0, mapMessage.externalPort, 0); - return result; - } - - return new PortMapAsyncResult(request, storedCallback, asyncState); - } - } -} diff --git a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs deleted file mode 100644 index 6f2716805..000000000 --- a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs +++ /dev/null @@ -1,110 +0,0 @@ -// -// Authors: -// Nicholas Terry <nick.i.terry@gmail.com> -// -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; - -namespace Mono.Nat.Upnp.Mappers -{ - internal class UpnpMapper : Upnp, IMapper - { - - public event EventHandler<DeviceEventArgs> DeviceFound; - - public UdpClient Client { get; set; } - - public UpnpMapper() - { - //Bind to local port 1900 for ssdp responses - Client = new UdpClient(1900); - } - - public void Map(IPAddress gatewayAddress) - { - //Get the httpu request payload - byte[] data = DiscoverDeviceMessage.EncodeUnicast(gatewayAddress); - - Client.Send(data, data.Length, new IPEndPoint(gatewayAddress, 1900)); - - new Thread(Receive).Start(); - } - - public void Receive() - { - while (true) - { - IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); - if (Client.Available > 0) - { - IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address; - byte[] data = Client.Receive(ref received); - Handle(localAddress, data, received); - } - } - } - - public void Handle(IPAddress localAddres, byte[] response) - { - Handle(localAddres, response, null); - } - - public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) - { - // No matter what, this method should never throw an exception. If something goes wrong - // we should still be in a position to handle the next reply correctly. - try - { - UpnpNatDevice d = base.Handle(localAddress, response, endpoint); - d.GetServicesList(DeviceSetupComplete); - } - catch (Exception ex) - { - Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: "); - Trace.WriteLine("ErrorMessage:"); - Trace.WriteLine(ex.Message); - Trace.WriteLine("Data string:"); - Trace.WriteLine(Encoding.UTF8.GetString(response)); - } - } - - private void DeviceSetupComplete(INatDevice device) - { - OnDeviceFound(new DeviceEventArgs(device)); - } - - private void OnDeviceFound(DeviceEventArgs args) - { - if (DeviceFound != null) - DeviceFound(this, args); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs index ce5270e9b..7c0c44d8e 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,8 +55,7 @@ namespace Mono.Nat.Upnp } #endregion - - public override System.Net.WebRequest Encode(out byte[] body) + public override HttpRequestOptions Encode() { throw new NotImplementedException(); } diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs index c5d7bce70..9d29f98fd 100644 --- a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs +++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs @@ -27,6 +27,8 @@ using System; using System.Diagnostics; using System.Net; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; namespace Mono.Nat.Upnp { @@ -34,28 +36,37 @@ namespace Mono.Nat.Upnp { private string servicesDescriptionUrl; private EndPoint hostAddress; + private readonly ILogger _logger; - public GetServicesMessage(string description, EndPoint hostAddress) - :base(null) + public GetServicesMessage(string description, EndPoint hostAddress, ILogger logger) + : base(null) { if (string.IsNullOrEmpty(description)) - Trace.WriteLine("Description is null"); + _logger.Warn("Description is null"); if (hostAddress == null) - Trace.WriteLine("hostaddress is null"); + _logger.Warn("hostaddress is null"); this.servicesDescriptionUrl = description; this.hostAddress = hostAddress; + _logger = logger; } + public override string Method + { + get + { + return "GET"; + } + } - public override WebRequest Encode(out byte[] body) + public override HttpRequestOptions Encode() { - HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl); - req.Headers.Add("ACCEPT-LANGUAGE", "en"); - req.Method = "GET"; + var req = new HttpRequestOptions(); + + req.Url = "http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl; + req.RequestHeaders.Add("ACCEPT-LANGUAGE", "en"); - body = new byte[0]; return req; } } diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs index da650fb41..e9caa916d 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,8 +52,7 @@ namespace Mono.Nat.Upnp } #endregion - - public override WebRequest Encode(out byte[] body) + public override HttpRequestOptions Encode() { CultureInfo culture = CultureInfo.InvariantCulture; @@ -69,7 +69,7 @@ namespace Mono.Nat.Upnp WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString()); writer.Flush(); - return CreateRequest("AddPortMapping", builder.ToString(), out body); + return CreateRequest("AddPortMapping", builder.ToString()); } } } diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs deleted file mode 100644 index d9be89a69..000000000 --- a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System.Net; -using System.IO; -using System.Text; -using System.Xml; - -namespace Mono.Nat.Upnp -{ - internal class DeletePortMappingMessage : MessageBase - { - private Mapping mapping; - - public DeletePortMappingMessage(Mapping mapping, UpnpNatDevice device) - : base(device) - { - this.mapping = mapping; - } - - public override WebRequest Encode(out byte[] body) - { - StringBuilder builder = new StringBuilder(256); - XmlWriter writer = CreateWriter(builder); - - WriteFullElement(writer, "NewRemoteHost", string.Empty); - WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture)); - WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"); - - writer.Flush(); - return CreateRequest("DeletePortMapping", builder.ToString(), out body); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs deleted file mode 100644 index 8f97002ea..000000000 --- a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; -using System.IO; - -namespace Mono.Nat.Upnp -{ - internal class GetExternalIPAddressMessage : MessageBase - { - - #region Constructors - public GetExternalIPAddressMessage(UpnpNatDevice device) - :base(device) - { - } - #endregion - - - public override WebRequest Encode(out byte[] body) - { - return CreateRequest("GetExternalIPAddress", string.Empty, out body); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs deleted file mode 100644 index c0c555881..000000000 --- a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml; - -namespace Mono.Nat.Upnp -{ - internal class GetGenericPortMappingEntry : MessageBase - { - private int index; - - public GetGenericPortMappingEntry(int index, UpnpNatDevice device) - :base(device) - { - this.index = index; - } - - public override System.Net.WebRequest Encode(out byte[] body) - { - StringBuilder sb = new StringBuilder(128); - XmlWriter writer = CreateWriter(sb); - - WriteFullElement(writer, "NewPortMappingIndex", index.ToString()); - - writer.Flush(); - return CreateRequest("GetGenericPortMappingEntry", sb.ToString(), out body); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs deleted file mode 100644 index 314468ece..000000000 --- a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml; -using System.Net; - -namespace Mono.Nat.Upnp -{ - internal class GetSpecificPortMappingEntryMessage : MessageBase - { - internal Protocol protocol; - internal int externalPort; - - public GetSpecificPortMappingEntryMessage(Protocol protocol, int externalPort, UpnpNatDevice device) - : base(device) - { - this.protocol = protocol; - this.externalPort = externalPort; - } - - public override WebRequest Encode(out byte[] body) - { - StringBuilder sb = new StringBuilder(64); - XmlWriter writer = CreateWriter(sb); - - WriteFullElement(writer, "NewRemoteHost", string.Empty); - WriteFullElement(writer, "NewExternalPort", externalPort.ToString()); - WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP"); - writer.Flush(); - - return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs index e75926b09..48776dd6f 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,7 +40,7 @@ namespace Mono.Nat.Upnp } #endregion - public override System.Net.WebRequest Encode(out byte[] body) + public override HttpRequestOptions Encode() { throw new NotImplementedException(); } diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs deleted file mode 100644 index 1fce4eb04..000000000 --- a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - - - -using System; -namespace Mono.Nat.Upnp -{ - internal class DeletePortMapResponseMessage : MessageBase - { - public DeletePortMapResponseMessage() - :base(null) - { - } - - public override System.Net.WebRequest Encode(out byte[] body) - { - throw new NotSupportedException(); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs deleted file mode 100644 index ee4b18cd1..000000000 --- a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; - -namespace Mono.Nat.Upnp -{ - internal class GetExternalIPAddressResponseMessage : MessageBase - { - public IPAddress ExternalIPAddress - { - get { return this.externalIPAddress; } - } - private IPAddress externalIPAddress; - - public GetExternalIPAddressResponseMessage(string ip) - :base(null) - { - this.externalIPAddress = IPAddress.Parse(ip); - } - - public override WebRequest Encode(out byte[] body) - { - throw new NotImplementedException(); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs deleted file mode 100644 index b11bfa027..000000000 --- a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs +++ /dev/null @@ -1,108 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml; - -namespace Mono.Nat.Upnp -{ - internal class GetGenericPortMappingEntryResponseMessage : MessageBase - { - private string remoteHost; - private int externalPort; - private Protocol protocol; - private int internalPort; - private string internalClient; - private bool enabled; - private string portMappingDescription; - private int leaseDuration; - - public string RemoteHost - { - get { return this.remoteHost; } - } - - public int ExternalPort - { - get { return this.externalPort; } - } - - public Protocol Protocol - { - get { return this.protocol; } - } - - public int InternalPort - { - get { return this.internalPort; } - } - - public string InternalClient - { - get { return this.internalClient; } - } - - public bool Enabled - { - get { return this.enabled; } - } - - public string PortMappingDescription - { - get { return this.portMappingDescription; } - } - - public int LeaseDuration - { - get { return this.leaseDuration; } - } - - - public GetGenericPortMappingEntryResponseMessage(XmlNode data, bool genericMapping) - : base(null) - { - remoteHost = (genericMapping) ? data["NewRemoteHost"].InnerText : string.Empty; - externalPort = (genericMapping) ? Convert.ToInt32(data["NewExternalPort"].InnerText) : -1; - if (genericMapping) - protocol = data["NewProtocol"].InnerText.Equals("TCP", StringComparison.InvariantCultureIgnoreCase) ? Protocol.Tcp : Protocol.Udp; - else - protocol = Protocol.Udp; - - internalPort = Convert.ToInt32(data["NewInternalPort"].InnerText); - internalClient = data["NewInternalClient"].InnerText; - enabled = data["NewEnabled"].InnerText == "1" ? true : false; - portMappingDescription = data["NewPortMappingDescription"].InnerText; - leaseDuration = Convert.ToInt32(data["NewLeaseDuration"].InnerText); - } - - public override System.Net.WebRequest Encode(out byte[] body) - { - throw new NotImplementedException(); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs index 44c16eec6..54cca4494 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 { @@ -44,17 +45,16 @@ namespace Mono.Nat.Upnp this.device = device; } - protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body) + protected HttpRequestOptions CreateRequest(string upnpMethod, string methodParameters) { string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl; NatUtility.Log("Initiating request to: {0}", ss); - Uri location = new Uri(ss); - HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(location); - req.KeepAlive = false; - req.Method = "POST"; - req.ContentType = "text/xml; charset=\"utf-8\""; - req.Headers.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); + var req = new HttpRequestOptions(); + req.Url = ss; + req.EnableKeepAlive = false; + req.RequestContentType = "text/xml; charset=\"utf-8\""; + req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); string bodyString = "<s:Envelope " + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " @@ -67,54 +67,17 @@ namespace Mono.Nat.Upnp + "</s:Body>" + "</s:Envelope>\r\n\r\n"; - body = System.Text.Encoding.UTF8.GetBytes(bodyString); + req.RequestContentBytes = System.Text.Encoding.UTF8.GetBytes(bodyString); return req; } - public static MessageBase Decode(UpnpNatDevice device, string message) - { - XmlNode node; - XmlDocument doc = new XmlDocument(); - doc.LoadXml(message); - - XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable); - - // Error messages should be found under this namespace - nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0"); - nsm.AddNamespace("responseNs", device.ServiceType); - - // Check to see if we have a fault code message. - if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) { - string errorCode = node["errorCode"] != null ? node["errorCode"].InnerText : ""; - string errorDescription = node["errorDescription"] != null ? node["errorDescription"].InnerText : ""; - - return new ErrorMessage(Convert.ToInt32(errorCode, CultureInfo.InvariantCulture), errorDescription); - } - - if ((doc.SelectSingleNode("//responseNs:AddPortMappingResponse", nsm)) != null) - return new CreatePortMappingResponseMessage(); + public abstract HttpRequestOptions Encode(); - if ((doc.SelectSingleNode("//responseNs:DeletePortMappingResponse", nsm)) != null) - return new DeletePortMapResponseMessage(); - - if ((node = doc.SelectSingleNode("//responseNs:GetExternalIPAddressResponse", nsm)) != null) { - string newExternalIPAddress = node["NewExternalIPAddress"] != null ? node["NewExternalIPAddress"].InnerText : ""; - return new GetExternalIPAddressResponseMessage(newExternalIPAddress); - } - - if ((node = doc.SelectSingleNode("//responseNs:GetGenericPortMappingEntryResponse", nsm)) != null) - return new GetGenericPortMappingEntryResponseMessage(node, true); - - if ((node = doc.SelectSingleNode("//responseNs:GetSpecificPortMappingEntryResponse", nsm)) != null) - return new GetGenericPortMappingEntryResponseMessage(node, false); - - NatUtility.Log("Unknown message returned. Please send me back the following XML:"); - NatUtility.Log(message); - return null; + public virtual string Method + { + get { return "POST"; } } - public abstract WebRequest Encode(out byte[] body); - 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 edc5a5d76..5e36410c5 100644 --- a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs +++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs @@ -36,97 +36,31 @@ using Mono.Nat.Upnp; using System.Diagnostics; using System.Net.Sockets; using System.Net.NetworkInformation; -using MediaBrowser.Controller.Dlna; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Dlna; namespace Mono.Nat { internal class UpnpSearcher : ISearcher { - private const int SearchPeriod = 5 * 60; // The time in seconds between each search - static UpnpSearcher instance = new UpnpSearcher(); - public static List<UdpClient> sockets = CreateSockets(); - - public static UpnpSearcher Instance - { - get { return instance; } - } - public event EventHandler<DeviceEventArgs> DeviceFound; public event EventHandler<DeviceEventArgs> DeviceLost; - private List<INatDevice> devices; - private Dictionary<IPAddress, DateTime> lastFetched; private DateTime nextSearch; - private IPEndPoint searchEndpoint; + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; - UpnpSearcher() + public UpnpSearcher(ILogger logger, IHttpClient httpClient) { - devices = new List<INatDevice>(); - lastFetched = new Dictionary<IPAddress, DateTime>(); - //searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); - searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); + _logger = logger; + _httpClient = httpClient; } - static List<UdpClient> CreateSockets() - { - List<UdpClient> clients = new List<UdpClient>(); - try - { - foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) - { - foreach (UnicastIPAddressInformation address in n.GetIPProperties().UnicastAddresses) - { - if (address.Address.AddressFamily == AddressFamily.InterNetwork) - { - try - { - clients.Add(new UdpClient(new IPEndPoint(address.Address, 0))); - } - catch - { - continue; // Move on to the next address. - } - } - } - } - } - catch (Exception) - { - clients.Add(new UdpClient(0)); - } - return clients; - } - public void Search() { - foreach (UdpClient s in sockets) - { - try - { - Search(s); - } - catch - { - // Ignore any search errors - } - } } - void Search(UdpClient client) - { - nextSearch = DateTime.Now.AddSeconds(SearchPeriod); - byte[] data = DiscoverDeviceMessage.EncodeSSDP(); - - // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2) - for (int i = 0; i < 3; i++) - client.Send(data, data.Length, searchEndpoint); - } - - public IPEndPoint SearchEndpoint - { - get { return searchEndpoint; } - } - public void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint) { // No matter what, this method should never throw an exception. If something goes wrong @@ -145,113 +79,19 @@ namespace Mono.Nat prefix. */ // We have an internet gateway device now - UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty); + UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty, _logger, _httpClient); - if (devices.Contains(d)) - { - // We already have found this device, so we just refresh it to let people know it's - // Still alive. If a device doesn't respond to a search, we dump it. - devices[devices.IndexOf(d)].LastSeen = DateTime.Now; - } - else - { - - // If we send 3 requests at a time, ensure we only fetch the services list once - // even if three responses are received - if (lastFetched.ContainsKey(endpoint.Address)) - { - DateTime last = lastFetched[endpoint.Address]; - if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) - return; - } - lastFetched[endpoint.Address] = DateTime.Now; - - // Once we've parsed the information we need, we tell the device to retrieve it's service list - // Once we successfully receive the service list, the callback provided will be invoked. - NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); - d.GetServicesList(DeviceSetupComplete); - } + NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); + OnDeviceFound(new DeviceEventArgs(d)); } catch (Exception ex) { - NatUtility.Log("Unhandled exception when trying to decode a device's response Send me the following data: "); - NatUtility.Log("ErrorMessage:"); - NatUtility.Log(ex.Message); + _logger.ErrorException("Error decoding device response", ex); } } public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) { - // Convert it to a string for easy parsing - string dataString = null; - - // No matter what, this method should never throw an exception. If something goes wrong - // we should still be in a position to handle the next reply correctly. - try { - string urn; - dataString = Encoding.UTF8.GetString(response); - - if (NatUtility.Verbose) - NatUtility.Log("UPnP Response: {0}", dataString); - - /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. - Any other device type is no good to us for this purpose. See the IGP overview paper - page 5 for an overview of device types and their hierarchy. - http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ - - /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which - version it is and apply the correct URN. */ - - /* Some routers don't correctly implement the version ID on the URN, so we only search for the type - prefix. */ - - string log = "UPnP Response: Router advertised a '{0}' service"; - StringComparison c = StringComparison.OrdinalIgnoreCase; - if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) { - urn = "urn:schemas-upnp-org:service:WANIPConnection:1"; - NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); - } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) { - urn = "urn:schemas-upnp-org:service:WANPPPConnection:1"; - NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); - } else - return; - - // We have an internet gateway device now - UpnpNatDevice d = new UpnpNatDevice(localAddress, dataString, urn); - - if (devices.Contains(d)) - { - // We already have found this device, so we just refresh it to let people know it's - // Still alive. If a device doesn't respond to a search, we dump it. - devices[devices.IndexOf(d)].LastSeen = DateTime.Now; - } - else - { - - // If we send 3 requests at a time, ensure we only fetch the services list once - // even if three responses are received - if (lastFetched.ContainsKey(endpoint.Address)) - { - DateTime last = lastFetched[endpoint.Address]; - if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) - return; - } - lastFetched[endpoint.Address] = DateTime.Now; - - // Once we've parsed the information we need, we tell the device to retrieve it's service list - // Once we successfully receive the service list, the callback provided will be invoked. - NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); - d.GetServicesList(DeviceSetupComplete); - } - } - catch (Exception ex) - { - Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: "); - Trace.WriteLine("ErrorMessage:"); - Trace.WriteLine(ex.Message); - Trace.WriteLine("Data string:"); - Trace.WriteLine(dataString); - } } public DateTime NextSearch @@ -259,20 +99,6 @@ namespace Mono.Nat get { return nextSearch; } } - private void DeviceSetupComplete(INatDevice device) - { - lock (this.devices) - { - // We don't want the same device in there twice - if (devices.Contains(device)) - return; - - devices.Add(device); - } - - OnDeviceFound(new DeviceEventArgs(device)); - } - private void OnDeviceFound(DeviceEventArgs args) { if (DeviceFound != null) diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs index e44a51c24..38d949250 100644 --- a/Mono.Nat/Upnp/Upnp.cs +++ b/Mono.Nat/Upnp/Upnp.cs @@ -33,12 +33,24 @@ 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 { internal class Upnp { - public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + 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; @@ -77,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); + 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 1160d3ac2..ebb1426d1 100644 --- a/Mono.Nat/Upnp/UpnpNatDevice.cs +++ b/Mono.Nat/Upnp/UpnpNatDevice.cs @@ -32,29 +32,29 @@ using System.Net; using System.Xml; using System.Text; using System.Diagnostics; -using MediaBrowser.Controller.Dlna; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Dlna; namespace Mono.Nat.Upnp { - public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice> - { - private EndPoint hostEndPoint; - private IPAddress localAddress; - private string serviceDescriptionUrl; - private string controlUrl; - private string serviceType; - - public override IPAddress LocalAddress - { - get { return localAddress; } - } - - /// <summary> - /// The callback to invoke when we are finished setting up the device - /// </summary> - private NatDeviceCallback callback; + public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice> + { + private EndPoint hostEndPoint; + private IPAddress localAddress; + private string serviceDescriptionUrl; + private string controlUrl; + private string serviceType; + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; + + public override IPAddress LocalAddress + { + get { return localAddress; } + } - internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType) + internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger, IHttpClient httpClient) { this.LastSeen = DateTime.Now; this.localAddress = localAddress; @@ -62,13 +62,15 @@ namespace Mono.Nat.Upnp // Split the string at the "location" section so i can extract the ipaddress and service description url string locationDetails = deviceInfo.Location.ToString(); this.serviceType = serviceType; + _logger = logger; + _httpClient = httpClient; // Make sure we have no excess whitespace locationDetails = locationDetails.Trim(); // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address // Are we going to get addresses with the "http://" attached? - if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) + if (locationDetails.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) { NatUtility.Log("Found device at: {0}", locationDetails); // This bit strings out the "http://" from the string @@ -88,30 +90,32 @@ namespace Mono.Nat.Upnp } } - internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType) - { - this.LastSeen = DateTime.Now; - this.localAddress = localAddress; + internal UpnpNatDevice(IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger, IHttpClient httpClient) + { + _logger = logger; + _httpClient = httpClient; + this.LastSeen = DateTime.Now; + this.localAddress = localAddress; - // Split the string at the "location" section so i can extract the ipaddress and service description url - string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.InvariantCultureIgnoreCase) + 9).Split('\r')[0]; + // 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(); + // Make sure we have no excess whitespace + locationDetails = locationDetails.Trim(); - // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address - // Are we going to get addresses with the "http://" attached? - if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) - { - NatUtility.Log("Found device at: {0}", locationDetails); - // This bit strings out the "http://" from the string - locationDetails = locationDetails.Substring(7); + // 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)) + { + 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(':'))), @@ -123,529 +127,85 @@ 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 - { - Trace.WriteLine("Couldn't decode address. Please send following string to the developer: "); - Trace.WriteLine(deviceDetails); - } - } + NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString()); - /// <summary> - /// The EndPoint that the device is at - /// </summary> - internal EndPoint HostEndPoint - { - get { return this.hostEndPoint; } - } + // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip + // and port information + this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); + } + else + { + logger.Warn("Couldn't decode address: " + deviceDetails); + } + } - /// <summary> - /// The relative url of the xml file that describes the list of services is at - /// </summary> - internal string ServiceDescriptionUrl - { - get { return this.serviceDescriptionUrl; } - } + /// <summary> + /// The EndPoint that the device is at + /// </summary> + internal EndPoint HostEndPoint + { + get { return this.hostEndPoint; } + } - /// <summary> - /// The relative url that we can use to control the port forwarding - /// </summary> - internal string ControlUrl - { - get { return this.controlUrl; } - } + /// <summary> + /// The relative url of the xml file that describes the list of services is at + /// </summary> + internal string ServiceDescriptionUrl + { + get { return this.serviceDescriptionUrl; } + } - /// <summary> - /// The service type we're using on the device - /// </summary> - public string ServiceType - { - get { return serviceType; } - } + /// <summary> + /// The relative url that we can use to control the port forwarding + /// </summary> + internal string ControlUrl + { + get { return this.controlUrl; } + } - /// <summary> - /// Begins an async call to get the external ip address of the router - /// </summary> - public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState) - { - // Create the port map message - GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this); - return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal); - } + /// <summary> + /// The service type we're using on the device + /// </summary> + public string ServiceType + { + get { return serviceType; } + } - /// <summary> - /// Maps the specified port to this computer - /// </summary> - public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState) - { + public override Task CreatePortMap(Mapping mapping) + { CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this); - return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal); - } - - /// <summary> - /// Removes a port mapping from this computer - /// </summary> - public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState) - { - DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this); - return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal); - } - - - public override IAsyncResult BeginGetAllMappings(AsyncCallback callback, object asyncState) - { - GetGenericPortMappingEntry message = new GetGenericPortMappingEntry(0, this); - return BeginMessageInternal(message, callback, asyncState, EndGetAllMappingsInternal); - } - - - public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState) - { - GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this); - return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal)); - } - - /// <summary> - /// - /// </summary> - /// <param name="result"></param> - public override void EndCreatePortMap(IAsyncResult result) - { - if (result == null) throw new ArgumentNullException("result"); - - PortMapAsyncResult mappingResult = result as PortMapAsyncResult; - if (mappingResult == null) - throw new ArgumentException("Invalid AsyncResult", "result"); - - // Check if we need to wait for the operation to finish - if (!result.IsCompleted) - result.AsyncWaitHandle.WaitOne(); - - // If we have a saved exception, it means something went wrong during the mapping - // so we just rethrow the exception and let the user figure out what they should do. - if (mappingResult.SavedMessage is ErrorMessage) - { - ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; - throw new MappingException(msg.ErrorCode, msg.Description); - } - - //return result.AsyncState as Mapping; - } - - - /// <summary> - /// - /// </summary> - /// <param name="result"></param> - public override void EndDeletePortMap(IAsyncResult result) - { - if (result == null) - throw new ArgumentNullException("result"); - - PortMapAsyncResult mappingResult = result as PortMapAsyncResult; - if (mappingResult == null) - throw new ArgumentException("Invalid AsyncResult", "result"); - - // Check if we need to wait for the operation to finish - if (!mappingResult.IsCompleted) - mappingResult.AsyncWaitHandle.WaitOne(); - - // If we have a saved exception, it means something went wrong during the mapping - // so we just rethrow the exception and let the user figure out what they should do. - if (mappingResult.SavedMessage is ErrorMessage) - { - ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; - throw new MappingException(msg.ErrorCode, msg.Description); - } - - // If all goes well, we just return - //return true; - } - - - public override Mapping[] EndGetAllMappings(IAsyncResult result) - { - if (result == null) - throw new ArgumentNullException("result"); - - GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; - if (mappingResult == null) - throw new ArgumentException("Invalid AsyncResult", "result"); - - if (!mappingResult.IsCompleted) - mappingResult.AsyncWaitHandle.WaitOne(); - - if (mappingResult.SavedMessage is ErrorMessage) - { - ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; - if (msg.ErrorCode != 713) - throw new MappingException(msg.ErrorCode, msg.Description); - } - - return mappingResult.Mappings.ToArray(); - } - - - /// <summary> - /// Ends an async request to get the external ip address of the router - /// </summary> - public override IPAddress EndGetExternalIP(IAsyncResult result) - { - if (result == null) throw new ArgumentNullException("result"); - - PortMapAsyncResult mappingResult = result as PortMapAsyncResult; - if (mappingResult == null) - throw new ArgumentException("Invalid AsyncResult", "result"); - - if (!result.IsCompleted) - result.AsyncWaitHandle.WaitOne(); - - if (mappingResult.SavedMessage is ErrorMessage) - { - ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; - throw new MappingException(msg.ErrorCode, msg.Description); - } - - if (mappingResult.SavedMessage == null) - return null; - else - return ((GetExternalIPAddressResponseMessage)mappingResult.SavedMessage).ExternalIPAddress; - } - - - public override Mapping EndGetSpecificMapping(IAsyncResult result) - { - if (result == null) - throw new ArgumentNullException("result"); - - GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; - if (mappingResult == null) - throw new ArgumentException("Invalid AsyncResult", "result"); - - if (!mappingResult.IsCompleted) - mappingResult.AsyncWaitHandle.WaitOne(); - - if (mappingResult.SavedMessage is ErrorMessage) - { - ErrorMessage message = mappingResult.SavedMessage as ErrorMessage; - if (message.ErrorCode != 0x2ca) - { - throw new MappingException(message.ErrorCode, message.Description); - } - } - if (mappingResult.Mappings.Count == 0) - return new Mapping (Protocol.Tcp, -1, -1); - - return mappingResult.Mappings[0]; - } - - - public override bool Equals(object obj) - { - UpnpNatDevice device = obj as UpnpNatDevice; - return (device == null) ? false : this.Equals((device)); - } - - - public bool Equals(UpnpNatDevice other) - { - return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint) - //&& this.controlUrl == other.controlUrl - && this.serviceDescriptionUrl == other.serviceDescriptionUrl); - } - - public override int GetHashCode() - { - return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode()); - } - - private IAsyncResult BeginMessageInternal(MessageBase message, AsyncCallback storedCallback, object asyncState, AsyncCallback callback) - { - byte[] body; - WebRequest request = message.Encode(out body); - PortMapAsyncResult mappingResult = PortMapAsyncResult.Create(message, request, storedCallback, asyncState); - - if (body.Length > 0) - { - request.ContentLength = body.Length; - request.BeginGetRequestStream(delegate(IAsyncResult result) { - try - { - Stream s = request.EndGetRequestStream(result); - s.Write(body, 0, body.Length); - request.BeginGetResponse(callback, mappingResult); - } - catch (Exception ex) - { - mappingResult.Complete(ex); - } - }, null); - } - else - { - request.BeginGetResponse(callback, mappingResult); - } - return mappingResult; - } - - private void CompleteMessage(IAsyncResult result) - { - PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult; - mappingResult.CompletedSynchronously = result.CompletedSynchronously; - mappingResult.Complete(); - } - - private MessageBase DecodeMessageFromResponse(Stream s, long length) - { - StringBuilder data = new StringBuilder(); - int bytesRead = 0; - int totalBytesRead = 0; - byte[] buffer = new byte[10240]; - - // Read out the content of the message, hopefully picking everything up in the case where we have no contentlength - if (length != -1) - { - while (totalBytesRead < length) - { - bytesRead = s.Read(buffer, 0, buffer.Length); - data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); - totalBytesRead += bytesRead; - } - } - else - { - while ((bytesRead = s.Read(buffer, 0, buffer.Length)) != 0) - data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); - } - - // Once we have our content, we need to see what kind of message it is. It'll either a an error - // or a response based on the action we performed. - return MessageBase.Decode(this, data.ToString()); - } - - private void EndCreatePortMapInternal(IAsyncResult result) - { - EndMessageInternal(result); - CompleteMessage(result); - } - - private void EndMessageInternal(IAsyncResult result) - { - HttpWebResponse response = null; - PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult; - - try - { - try - { - response = (HttpWebResponse)mappingResult.Request.EndGetResponse(result); - } - catch (WebException ex) - { - // Even if the request "failed" i want to continue on to read out the response from the router - response = ex.Response as HttpWebResponse; - if (response == null) - mappingResult.SavedMessage = new ErrorMessage((int)ex.Status, ex.Message); - } - if (response != null) - mappingResult.SavedMessage = DecodeMessageFromResponse(response.GetResponseStream(), response.ContentLength); - } - - finally - { - if (response != null) - response.Close(); - } - } - - private void EndDeletePortMapInternal(IAsyncResult result) - { - EndMessageInternal(result); - CompleteMessage(result); - } - - private void EndGetAllMappingsInternal(IAsyncResult result) - { - EndMessageInternal(result); - - GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult; - GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; - if (message != null) - { - Mapping mapping = new Mapping (message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration); - mapping.Description = message.PortMappingDescription; - mappingResult.Mappings.Add(mapping); - GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this); - - // It's ok to do this synchronously because we should already be on anther thread - // and this won't block the user. - byte[] body; - WebRequest request = next.Encode(out body); - if (body.Length > 0) - { - request.ContentLength = body.Length; - request.GetRequestStream().Write(body, 0, body.Length); - } - mappingResult.Request = request; - request.BeginGetResponse(EndGetAllMappingsInternal, mappingResult); - return; - } - - CompleteMessage(result); - } - - private void EndGetExternalIPInternal(IAsyncResult result) - { - EndMessageInternal(result); - CompleteMessage(result); - } - - private void EndGetSpecificMappingInternal(IAsyncResult result) - { - EndMessageInternal(result); - - GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult; - GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; - if (message != null) { - Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration); - mapping.Description = mappingResult.SpecificMapping.Description; - mappingResult.Mappings.Add(mapping); - } - - CompleteMessage(result); - } - - internal void GetServicesList(NatDeviceCallback callback) - { - // Save the callback so i can use it again later when i've finished parsing the services available - this.callback = callback; - - // Create a HTTPWebRequest to download the list of services the device offers - byte[] body; - WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint).Encode(out body); - if (body.Length > 0) - NatUtility.Log("Error: Services Message contained a body"); - request.BeginGetResponse(this.ServicesReceived, request); - } - - private void ServicesReceived(IAsyncResult result) - { - HttpWebResponse response = null; - try - { - int abortCount = 0; - int bytesRead = 0; - byte[] buffer = new byte[10240]; - StringBuilder servicesXml = new StringBuilder(); - XmlDocument xmldoc = new XmlDocument(); - HttpWebRequest request = result.AsyncState as HttpWebRequest; - response = request.EndGetResponse(result) as HttpWebResponse; - Stream s = response.GetResponseStream(); - - if (response.StatusCode != HttpStatusCode.OK) { - NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode); - return; // FIXME: This the best thing to do?? - } + return _httpClient.SendAsync(message.Encode(), message.Method); + } - 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); - } - } + public override bool Equals(object obj) + { + UpnpNatDevice device = obj as UpnpNatDevice; + return (device == null) ? false : this.Equals((device)); + } - 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; - } - } - } + public bool Equals(UpnpNatDevice other) + { + return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint) + //&& this.controlUrl == other.controlUrl + && this.serviceDescriptionUrl == other.serviceDescriptionUrl); + } - //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(); - } - } + public override int GetHashCode() + { + return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode()); + } /// <summary> /// Overridden. /// </summary> /// <returns></returns> - public override string ToString( ) + public override string ToString() { //GetExternalIP is blocking and can throw exceptions, can't use it here. - return String.Format( + return String.Format( "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}", this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen); } - } + } }
\ No newline at end of file diff --git a/Mono.Nat/project.json b/Mono.Nat/project.json new file mode 100644 index 000000000..3c38a62e1 --- /dev/null +++ b/Mono.Nat/project.json @@ -0,0 +1,41 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + + }, + + "frameworks": { + "net46": { + "frameworkAssemblies": { + "System.Collections": "4.0.0.0", + "System.Net": "4.0.0.0", + "System.Runtime": "4.0.0.0", + "System.Threading": "4.0.0.0", + "System.Threading.Tasks": "4.0.0.0", + "System.Xml": "4.0.0.0" + }, + "dependencies": { + "MediaBrowser.Common": { + "target": "project" + }, + "MediaBrowser.Model": { + "target": "project" + } + } + }, + "netstandard1.6": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.6.1", + "MediaBrowser.Common": { + "target": "project" + }, + "MediaBrowser.Model": { + "target": "project" + }, + "System.Net.NetworkInformation": "4.3.0" + } + } + } +} |
