update port mapper

pull/702/head
Luke Pulverenti 8 years ago
parent 8e57296f69
commit ce043225c4

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

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

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

@ -54,7 +54,6 @@
<Compile Include="NatUtility.cs" /> <Compile Include="NatUtility.cs" />
<Compile Include="Pmp\AsyncResults\PortMapAsyncResult.cs" /> <Compile Include="Pmp\AsyncResults\PortMapAsyncResult.cs" />
<Compile Include="Pmp\Mappers\PmpMapper.cs" /> <Compile Include="Pmp\Mappers\PmpMapper.cs" />
<Compile Include="Pmp\Pmp.cs" />
<Compile Include="Pmp\PmpConstants.cs" /> <Compile Include="Pmp\PmpConstants.cs" />
<Compile Include="Pmp\PmpNatDevice.cs" /> <Compile Include="Pmp\PmpNatDevice.cs" />
<Compile Include="Pmp\Searchers\PmpSearcher.cs" /> <Compile Include="Pmp\Searchers\PmpSearcher.cs" />
@ -80,6 +79,10 @@
<Compile Include="Upnp\UpnpNatDevice.cs" /> <Compile Include="Upnp\UpnpNatDevice.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
<Name>MediaBrowser.Controller</Name> <Name>MediaBrowser.Controller</Name>

@ -34,9 +34,11 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using Mono.Nat.Pmp.Mappers; using Mono.Nat.Pmp.Mappers;
using Mono.Nat.Upnp;
using Mono.Nat.Upnp.Mappers; using Mono.Nat.Upnp.Mappers;
namespace Mono.Nat namespace Mono.Nat
@ -55,6 +57,7 @@ namespace Mono.Nat
public static List<NatProtocol> EnabledProtocols { get; set; } public static List<NatProtocol> EnabledProtocols { get; set; }
public static ILogger Logger { get; set; } public static ILogger Logger { get; set; }
public static IHttpClient HttpClient { get; set; }
public static bool Verbose public static bool Verbose
{ {
@ -154,32 +157,6 @@ namespace Mono.Nat
searching.Reset(); searching.Reset();
} }
//This is for when you know the Gateway IP and want to skip the costly search...
public static void DirectMap(IPAddress gatewayAddress, MapperType type)
{
IMapper mapper;
switch (type)
{
case MapperType.Pmp:
mapper = new PmpMapper();
break;
case MapperType.Upnp:
mapper = new UpnpMapper(Logger);
mapper.DeviceFound += (sender, args) =>
{
if (DeviceFound != null)
DeviceFound(sender, args);
};
mapper.Map(gatewayAddress);
break;
default:
throw new InvalidOperationException("Unsuported type given");
}
searching.Reset();
}
//checks if an IP address is a private address space as defined by RFC 1918 //checks if an IP address is a private address space as defined by RFC 1918
public static bool IsPrivateAddressSpace (IPAddress address) public static bool IsPrivateAddressSpace (IPAddress address)
{ {
@ -217,11 +194,21 @@ namespace Mono.Nat
switch (protocol) switch (protocol)
{ {
case NatProtocol.Upnp: case NatProtocol.Upnp:
new UpnpSearcher(Logger).Handle(localAddress, deviceInfo, endpoint); var searcher = new UpnpSearcher(Logger, HttpClient);
searcher.DeviceFound += Searcher_DeviceFound;
searcher.Handle(localAddress, deviceInfo, endpoint);
break; break;
default: default:
throw new ArgumentException("Unexpected protocol: " + protocol); throw new ArgumentException("Unexpected protocol: " + protocol);
} }
} }
private static void Searcher_DeviceFound(object sender, DeviceEventArgs e)
{
if (DeviceFound != null)
{
DeviceFound(sender, e);
}
}
} }
} }

@ -34,26 +34,12 @@ using Mono.Nat.Pmp;
namespace Mono.Nat.Pmp.Mappers namespace Mono.Nat.Pmp.Mappers
{ {
internal class PmpMapper : Pmp, IMapper internal class PmpMapper : IMapper
{ {
public event EventHandler<DeviceEventArgs> DeviceFound; public event EventHandler<DeviceEventArgs> DeviceFound;
static PmpMapper()
{
CreateSocketsAndAddGateways();
}
public void Map(IPAddress gatewayAddress) 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) public void Handle(IPAddress localAddres, byte[] response)

@ -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.
}
}
}
}

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

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

@ -32,19 +32,20 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
namespace Mono.Nat.Upnp.Mappers namespace Mono.Nat.Upnp.Mappers
{ {
internal class UpnpMapper : Upnp, IMapper internal class UpnpMapper : Upnp, IMapper
{ {
public event EventHandler<DeviceEventArgs> DeviceFound; public event EventHandler<DeviceEventArgs> DeviceFound;
public UdpClient Client { get; set; } public UdpClient Client { get; set; }
public UpnpMapper(ILogger logger) public UpnpMapper(ILogger logger, IHttpClient httpClient)
: base(logger) : base(logger, httpClient)
{ {
//Bind to local port 1900 for ssdp responses //Bind to local port 1900 for ssdp responses
Client = new UdpClient(1900); Client = new UdpClient(1900);
@ -60,7 +61,7 @@ namespace Mono.Nat.Upnp.Mappers
new Thread(Receive).Start(); new Thread(Receive).Start();
} }
public void Receive() public async void Receive()
{ {
while (true) while (true)
{ {
@ -69,28 +70,36 @@ namespace Mono.Nat.Upnp.Mappers
{ {
IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address; IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address;
byte[] data = Client.Receive(ref received); byte[] data = Client.Receive(ref received);
Handle(localAddress, data, received);
await Handle(localAddress, data, received);
} }
} }
} }
public void Handle(IPAddress localAddres, byte[] response) public void Handle(IPAddress localAddres, byte[] response)
{ {
Handle(localAddres, response, null);
} }
public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) public override async Task<UpnpNatDevice> Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
{ {
// No matter what, this method should never throw an exception. If something goes wrong // No matter what, this method should never throw an exception. If something goes wrong
// we should still be in a position to handle the next reply correctly. // we should still be in a position to handle the next reply correctly.
try try
{ {
UpnpNatDevice d = base.Handle(localAddress, response, endpoint); var d = await base.Handle(localAddress, response, endpoint).ConfigureAwait(false);
d.GetServicesList(DeviceSetupComplete); var result = await d.GetServicesList().ConfigureAwait(false);
if (result)
{
DeviceSetupComplete(d);
}
return d;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.ErrorException("Error mapping port. Data string: {0}", ex, Encoding.UTF8.GetString(response)); Logger.ErrorException("Error mapping port. Data string: {0}", ex, Encoding.UTF8.GetString(response));
return null;
} }
} }

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

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

@ -29,6 +29,7 @@ using System.IO;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using MediaBrowser.Common.Net;
namespace Mono.Nat.Upnp namespace Mono.Nat.Upnp
{ {
@ -51,6 +52,25 @@ namespace Mono.Nat.Upnp
} }
#endregion #endregion
public override HttpRequestOptions Encode()
{
CultureInfo culture = CultureInfo.InvariantCulture;
StringBuilder builder = new StringBuilder(256);
XmlWriter writer = CreateWriter(builder);
WriteFullElement(writer, "NewRemoteHost", string.Empty);
WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture));
WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture));
WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString());
WriteFullElement(writer, "NewEnabled", "1");
WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description);
WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString());
writer.Flush();
return CreateRequest("AddPortMapping", builder.ToString());
}
public override WebRequest Encode(out byte[] body) public override WebRequest Encode(out byte[] body)
{ {

@ -28,6 +28,7 @@ using System.Net;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using MediaBrowser.Common.Net;
namespace Mono.Nat.Upnp namespace Mono.Nat.Upnp
{ {
@ -41,6 +42,19 @@ namespace Mono.Nat.Upnp
this.mapping = mapping; this.mapping = mapping;
} }
public override HttpRequestOptions Encode()
{
StringBuilder builder = new StringBuilder(256);
XmlWriter writer = CreateWriter(builder);
WriteFullElement(writer, "NewRemoteHost", string.Empty);
WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture));
WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
writer.Flush();
return CreateRequest("DeletePortMapping", builder.ToString());
}
public override WebRequest Encode(out byte[] body) public override WebRequest Encode(out byte[] body)
{ {
StringBuilder builder = new StringBuilder(256); StringBuilder builder = new StringBuilder(256);

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

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

@ -29,6 +29,7 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using System.Net; using System.Net;
using MediaBrowser.Common.Net;
namespace Mono.Nat.Upnp namespace Mono.Nat.Upnp
{ {
@ -56,5 +57,18 @@ namespace Mono.Nat.Upnp
return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body); return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body);
} }
public override HttpRequestOptions Encode()
{
StringBuilder sb = new StringBuilder(64);
XmlWriter writer = CreateWriter(sb);
WriteFullElement(writer, "NewRemoteHost", string.Empty);
WriteFullElement(writer, "NewExternalPort", externalPort.ToString());
WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP");
writer.Flush();
return CreateRequest("GetSpecificPortMappingEntry", sb.ToString());
}
} }
} }

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

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

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

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

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

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

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

@ -32,6 +32,8 @@ using System.Net;
using System.Xml; using System.Xml;
using System.Text; using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -45,18 +47,14 @@ namespace Mono.Nat.Upnp
private string controlUrl; private string controlUrl;
private string serviceType; private string serviceType;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
public override IPAddress LocalAddress public override IPAddress LocalAddress
{ {
get { return localAddress; } get { return localAddress; }
} }
/// <summary> internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger, IHttpClient httpClient)
/// The callback to invoke when we are finished setting up the device
/// </summary>
private NatDeviceCallback callback;
internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger)
{ {
this.LastSeen = DateTime.Now; this.LastSeen = DateTime.Now;
this.localAddress = localAddress; this.localAddress = localAddress;
@ -65,6 +63,7 @@ namespace Mono.Nat.Upnp
string locationDetails = deviceInfo.Location.ToString(); string locationDetails = deviceInfo.Location.ToString();
this.serviceType = serviceType; this.serviceType = serviceType;
_logger = logger; _logger = logger;
_httpClient = httpClient;
// Make sure we have no excess whitespace // Make sure we have no excess whitespace
locationDetails = locationDetails.Trim(); locationDetails = locationDetails.Trim();
@ -91,9 +90,10 @@ namespace Mono.Nat.Upnp
} }
} }
internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger) internal UpnpNatDevice(IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger, IHttpClient httpClient)
{ {
_logger = logger; _logger = logger;
_httpClient = httpClient;
this.LastSeen = DateTime.Now; this.LastSeen = DateTime.Now;
this.localAddress = localAddress; this.localAddress = localAddress;
@ -190,6 +190,12 @@ namespace Mono.Nat.Upnp
return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal); return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal);
} }
public override Task CreatePortMap(Mapping mapping)
{
CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
return _httpClient.SendAsync(message.Encode(), message.Method);
}
/// <summary> /// <summary>
/// Removes a port mapping from this computer /// Removes a port mapping from this computer
/// </summary> /// </summary>
@ -207,7 +213,7 @@ namespace Mono.Nat.Upnp
} }
public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState) public override IAsyncResult BeginGetSpecificMapping(Protocol protocol, int port, AsyncCallback callback, object asyncState)
{ {
GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this); GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this);
return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal)); return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal));
@ -342,7 +348,7 @@ namespace Mono.Nat.Upnp
} }
} }
if (mappingResult.Mappings.Count == 0) if (mappingResult.Mappings.Count == 0)
return new Mapping (Protocol.Tcp, -1, -1); return new Mapping(Protocol.Tcp, -1, -1);
return mappingResult.Mappings[0]; return mappingResult.Mappings[0];
} }
@ -376,7 +382,8 @@ namespace Mono.Nat.Upnp
if (body.Length > 0) if (body.Length > 0)
{ {
request.ContentLength = body.Length; request.ContentLength = body.Length;
request.BeginGetRequestStream(delegate(IAsyncResult result) { request.BeginGetRequestStream(delegate (IAsyncResult result)
{
try try
{ {
Stream s = request.EndGetRequestStream(result); Stream s = request.EndGetRequestStream(result);
@ -480,7 +487,7 @@ namespace Mono.Nat.Upnp
GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
if (message != null) if (message != null)
{ {
Mapping mapping = new Mapping (message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration); Mapping mapping = new Mapping(message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration);
mapping.Description = message.PortMappingDescription; mapping.Description = message.PortMappingDescription;
mappingResult.Mappings.Add(mapping); mappingResult.Mappings.Add(mapping);
GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this); GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this);
@ -514,7 +521,8 @@ namespace Mono.Nat.Upnp
GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult; GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
if (message != null) { if (message != null)
{
Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration); Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration);
mapping.Description = mappingResult.SpecificMapping.Description; mapping.Description = mappingResult.SpecificMapping.Description;
mappingResult.Mappings.Add(mapping); mappingResult.Mappings.Add(mapping);
@ -523,37 +531,26 @@ namespace Mono.Nat.Upnp
CompleteMessage(result); CompleteMessage(result);
} }
internal void GetServicesList(NatDeviceCallback callback) internal async Task<bool> GetServicesList()
{ {
// 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 // Create a HTTPWebRequest to download the list of services the device offers
byte[] body; var requestOptions = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger).Encode();
WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger).Encode(out body);
if (body.Length > 0)
NatUtility.Log("Error: Services Message contained a body");
request.BeginGetResponse(this.ServicesReceived, request);
}
private void ServicesReceived(IAsyncResult result) requestOptions.BufferContent = false;
using (var response = await _httpClient.Get(requestOptions).ConfigureAwait(false))
{ {
HttpWebResponse response = null; return ServicesReceived(response);
try }
}
private bool ServicesReceived(Stream s)
{ {
int abortCount = 0; int abortCount = 0;
int bytesRead = 0; int bytesRead = 0;
byte[] buffer = new byte[10240]; byte[] buffer = new byte[10240];
StringBuilder servicesXml = new StringBuilder(); StringBuilder servicesXml = new StringBuilder();
XmlDocument xmldoc = new XmlDocument(); XmlDocument xmldoc = new XmlDocument();
HttpWebRequest request = result.AsyncState as HttpWebRequest;
response = request.EndGetResponse(result) as HttpWebResponse;
Stream s = response.GetResponseStream();
if (response.StatusCode != HttpStatusCode.OK) {
NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode);
return; // FIXME: This the best thing to do??
}
while (true) while (true)
{ {
@ -562,7 +559,6 @@ namespace Mono.Nat.Upnp
try try
{ {
xmldoc.LoadXml(servicesXml.ToString()); xmldoc.LoadXml(servicesXml.ToString());
response.Close();
break; break;
} }
catch (XmlException) catch (XmlException)
@ -573,8 +569,7 @@ namespace Mono.Nat.Upnp
// parsed by the xmldoc. Without this, the code will never pick up my router. // parsed by the xmldoc. Without this, the code will never pick up my router.
if (abortCount++ > 50) if (abortCount++ > 50)
{ {
response.Close(); return false;
return;
} }
NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint); NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint);
System.Threading.Thread.Sleep(10); System.Threading.Thread.Sleep(10);
@ -618,32 +613,21 @@ namespace Mono.Nat.Upnp
NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl); NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl);
} }
NatUtility.Log("{0}: Handshake Complete", HostEndPoint); NatUtility.Log("{0}: Handshake Complete", HostEndPoint);
this.callback(this); return true;
return;
} }
} }
} }
//If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding //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 //So we don't invoke the callback, so this device is never added to our lists
} return false;
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();
}
} }
/// <summary> /// <summary>
/// Overridden. /// Overridden.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public override string ToString( ) public override string ToString()
{ {
//GetExternalIP is blocking and can throw exceptions, can't use it here. //GetExternalIP is blocking and can throw exceptions, can't use it here.
return String.Format( return String.Format(

Loading…
Cancel
Save