aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2016-09-11 03:33:53 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2016-09-11 03:33:53 -0400
commit62d9eb1ec7da1b7017818e5620c2334ad336ac2f (patch)
tree6a9a158fbf7be50c4789d7991fe0d4c3d924d5ab
parentb4217f2800f89658d9ffc2651327d4d6582e41bd (diff)
rework upnp discovery
-rw-r--r--MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs14
-rw-r--r--MediaBrowser.Controller/Dlna/ISsdpHandler.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs2
-rw-r--r--MediaBrowser.Dlna/Main/DlnaEntryPoint.cs86
-rw-r--r--MediaBrowser.Dlna/MediaBrowser.Dlna.csproj12
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToController.cs13
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToManager.cs26
-rw-r--r--MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs213
-rw-r--r--MediaBrowser.Dlna/Ssdp/SsdpHandler.cs383
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj3
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj5
-rw-r--r--MediaBrowser.Model/Configuration/AutoOnOff.cs10
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs3
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs66
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs20
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs86
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs9
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs11
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj7
-rw-r--r--MediaBrowser.Server.Implementations/packages.config1
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj5
-rw-r--r--MediaBrowser.Server.Mono/app.config14
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs4
-rw-r--r--MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj3
-rw-r--r--MediaBrowser.sln32
-rw-r--r--Mono.Nat/AbstractNatDevice.cs97
-rw-r--r--Mono.Nat/AsyncResults/AsyncResult.cs71
-rw-r--r--Mono.Nat/Enums/MapState.cs36
-rw-r--r--Mono.Nat/Enums/ProtocolType.cs36
-rw-r--r--Mono.Nat/EventArgs/DeviceEventArgs.cs45
-rw-r--r--Mono.Nat/Exceptions/MappingException.cs87
-rw-r--r--Mono.Nat/IMapper.cs50
-rw-r--r--Mono.Nat/INatDevice.cs62
-rw-r--r--Mono.Nat/ISearcher.cs51
-rw-r--r--Mono.Nat/Mapping.cs123
-rw-r--r--Mono.Nat/Mono.Nat.csproj104
-rw-r--r--Mono.Nat/NatProtocol.cs9
-rw-r--r--Mono.Nat/NatUtility.cs264
-rw-r--r--Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs52
-rw-r--r--Mono.Nat/Pmp/Mappers/PmpMapper.cs83
-rw-r--r--Mono.Nat/Pmp/Pmp.cs118
-rw-r--r--Mono.Nat/Pmp/PmpConstants.cs56
-rw-r--r--Mono.Nat/Pmp/PmpNatDevice.cs347
-rw-r--r--Mono.Nat/Pmp/Searchers/PmpSearcher.cs149
-rw-r--r--Mono.Nat/Properties/AssemblyInfo.cs31
-rw-r--r--Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs56
-rw-r--r--Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs75
-rw-r--r--Mono.Nat/Upnp/Mappers/UpnpMapper.cs110
-rw-r--r--Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/ErrorMessage.cs63
-rw-r--r--Mono.Nat/Upnp/Messages/GetServicesMessage.cs62
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs75
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs57
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs51
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs55
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs46
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs44
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs53
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs108
-rw-r--r--Mono.Nat/Upnp/Messages/UpnpMessage.cs132
-rw-r--r--Mono.Nat/Upnp/Searchers/UpnpSearcher.cs287
-rw-r--r--Mono.Nat/Upnp/Upnp.cs83
-rw-r--r--Mono.Nat/Upnp/UpnpNatDevice.cs651
65 files changed, 4329 insertions, 700 deletions
diff --git a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
index e8083b363..d2c5b9e4e 100644
--- a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
+++ b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
@@ -1,10 +1,20 @@
using System;
+using System.Collections.Generic;
+using System.Net;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Controller.Dlna
{
public interface IDeviceDiscovery
{
- event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
- event EventHandler<SsdpMessageEventArgs> DeviceLeft;
+ event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+ event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
+ }
+
+ public class UpnpDeviceInfo
+ {
+ public Uri Location { get; set; }
+ public Dictionary<string, string> Headers { get; set; }
+ public IPEndPoint LocalEndPoint { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
index e4126ddcf..ec3a00aad 100644
--- a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
+++ b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
@@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna
{
public interface ISsdpHandler
{
- event EventHandler<SsdpMessageEventArgs> MessageReceived;
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index ea5e6dbc6..d0377fbfd 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -107,6 +107,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The image URL.</value>
public string ImageUrl { get; set; }
+ public string LogoImageUrl { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance has image.
/// </summary>
diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
index 9f2726b31..af03f325f 100644
--- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
+++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
@@ -14,8 +14,10 @@ using MediaBrowser.Dlna.Ssdp;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
+using Rssdp;
namespace MediaBrowser.Dlna.Main
{
@@ -38,12 +40,11 @@ namespace MediaBrowser.Dlna.Main
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly SsdpHandler _ssdpHandler;
private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly List<string> _registeredServerIds = new List<string>();
private bool _ssdpHandlerStarted;
private bool _dlnaServerStarted;
+ private SsdpDevicePublisher _Publisher;
public DlnaEntryPoint(IServerConfigurationManager config,
ILogManager logManager,
@@ -58,7 +59,7 @@ namespace MediaBrowser.Dlna.Main
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
- ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
+ IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
{
_config = config;
_appHost = appHost;
@@ -74,7 +75,6 @@ namespace MediaBrowser.Dlna.Main
_mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder;
- _ssdpHandler = (SsdpHandler)ssdpHandler;
_logger = logManager.GetLogger("Dlna");
}
@@ -154,7 +154,7 @@ namespace MediaBrowser.Dlna.Main
{
try
{
- _ssdpHandler.Start();
+ StartPublishing();
_ssdpHandlerStarted = true;
StartDeviceDiscovery();
@@ -165,13 +165,16 @@ namespace MediaBrowser.Dlna.Main
}
}
+ private void StartPublishing()
+ {
+ _Publisher = new SsdpDevicePublisher();
+ }
+
private void StartDeviceDiscovery()
{
try
{
- ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler);
-
- //DlnaChannel.Current.Start(() => _registeredServerIds.ToList());
+ ((DeviceDiscovery)_deviceDiscovery).Start();
}
catch (Exception ex)
{
@@ -199,8 +202,6 @@ namespace MediaBrowser.Dlna.Main
{
((DeviceDiscovery)_deviceDiscovery).Dispose();
- _ssdpHandler.Dispose();
-
_ssdpHandlerStarted = false;
}
catch (Exception ex)
@@ -225,6 +226,14 @@ namespace MediaBrowser.Dlna.Main
private async Task RegisterServerEndpoints()
{
+ if (!_config.GetDlnaConfiguration().BlastAliveMessages)
+ {
+ return;
+ }
+
+ var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds*2;
+ _Publisher.SupportPnpRootDevice = true;
+
foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false))
{
//if (IPAddress.IsLoopback(address))
@@ -234,25 +243,41 @@ namespace MediaBrowser.Dlna.Main
//}
var addressString = address.ToString();
- var udn = addressString.GetMD5().ToString("N");
-
- var descriptorURI = "/dlna/" + udn + "/description.xml";
-
- var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
var services = new List<string>
{
- "upnp:rootdevice",
"urn:schemas-upnp-org:device:MediaServer:1",
"urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1",
- "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
- "uuid:" + udn
+ "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
};
- _ssdpHandler.RegisterNotification(udn, uri, address, services);
+ var udn = (addressString).GetMD5().ToString("N");
+
+ foreach (var fullService in services)
+ {
+ var descriptorURI = "/dlna/" + udn + "/description.xml";
+ var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
+
+ var service = fullService.Replace("urn:", string.Empty).Replace(":1", string.Empty);
- _registeredServerIds.Add(udn);
+ var serviceParts = service.Split(':');
+
+ var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
+
+ _Publisher.AddDevice(new SsdpRootDevice
+ {
+ CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info.
+ Location = uri, // Must point to the URL that serves your devices UPnP description document.
+ DeviceTypeNamespace = deviceTypeNamespace,
+ DeviceClass = serviceParts[1],
+ DeviceType = serviceParts[2],
+ FriendlyName = "Emby Server",
+ Manufacturer = "Emby",
+ ModelName = "Emby Server",
+ Uuid = udn // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
+ });
+ }
}
}
@@ -315,20 +340,23 @@ namespace MediaBrowser.Dlna.Main
public void DisposeDlnaServer()
{
- foreach (var id in _registeredServerIds)
+ if (_Publisher != null)
{
- try
- {
- _ssdpHandler.UnregisterNotification(id);
- }
- catch (Exception ex)
+ var devices = _Publisher.Devices.ToList();
+ foreach (var device in devices)
{
- _logger.ErrorException("Error unregistering server", ex);
+ try
+ {
+ _Publisher.RemoveDevice(device);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error sending bye bye", ex);
+ }
}
+ _Publisher.Dispose();
}
- _registeredServerIds.Clear();
-
_dlnaServerStarted = false;
}
}
diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
index d10a5f7b5..b25376d1b 100644
--- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
+++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.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>
@@ -14,6 +14,7 @@
<SchemaVersion>2.0</SchemaVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -23,7 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@@ -50,8 +51,15 @@
<Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference>
+ <Reference Include="Rssdp.NetFx40">
+ <HintPath>..\ThirdParty\rssdp\Rssdp.NetFx40.dll</HintPath>
+ </Reference>
+ <Reference Include="Rssdp.Portable">
+ <HintPath>..\ThirdParty\rssdp\Rssdp.Portable.dll</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
+ <Reference Include="System.Net.Http" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
index 5622885fc..d958d0e37 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
@@ -19,6 +19,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -122,16 +123,18 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string nts;
- e.Headers.TryGetValue("NTS", out nts);
+ info.Headers.TryGetValue("NTS", out nts);
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
!_disposed)
@@ -653,7 +656,7 @@ namespace MediaBrowser.Dlna.PlayTo
_device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped;
_device.MediaChanged -= _device_MediaChanged;
- _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
+ //_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
_device.OnDeviceUnavailable = null;
_device.Dispose();
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
index cd9a7b1f0..6d6986f01 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
@@ -12,7 +12,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
+using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -61,16 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
- string location;
- if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty;
+ string location = info.Location.ToString();
// It has to report that it's a media renderer
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -100,7 +103,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- var uri = new Uri(location);
+ var uri = info.Location;
_logger.Debug("Attempting to create PlayToController from location {0}", location);
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
@@ -121,7 +124,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (controller == null)
{
- var serverAddress = GetServerAddress(e.LocalEndPoint.Address);
+ var serverAddress = await GetServerAddress(info.LocalEndPoint == null ? null : info.LocalEndPoint.Address).ConfigureAwait(false);
string accessToken = null;
sessionInfo.SessionController = controller = new PlayToController(sessionInfo,
@@ -173,9 +176,14 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- private string GetServerAddress(IPAddress localIp)
+ private Task<string> GetServerAddress(IPAddress localIp)
{
- return _appHost.GetLocalApiUrl(localIp);
+ if (localIp == null)
+ {
+ return _appHost.GetLocalApiUrl();
+ }
+
+ return Task.FromResult(_appHost.GetLocalApiUrl(localIp));
}
public void Dispose()
diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
index 68768745e..91dbeb96e 100644
--- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
@@ -11,6 +11,8 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
+using Rssdp;
namespace MediaBrowser.Dlna.Ssdp
{
@@ -20,132 +22,43 @@ namespace MediaBrowser.Dlna.Ssdp
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
- private SsdpHandler _ssdpHandler;
private readonly CancellationTokenSource _tokenSource;
- private readonly IServerApplicationHost _appHost;
- public event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
- public event EventHandler<SsdpMessageEventArgs> DeviceLeft;
- private readonly INetworkManager _networkManager;
+ public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+ public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
- public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost, INetworkManager networkManager)
+ private SsdpDeviceLocator _DeviceLocator;
+
+ public DeviceDiscovery(ILogger logger, IServerConfigurationManager config)
{
_tokenSource = new CancellationTokenSource();
_logger = logger;
_config = config;
- _appHost = appHost;
- _networkManager = networkManager;
- }
-
- private List<IPAddress> GetLocalIpAddresses()
- {
- return _networkManager.GetLocalIpAddresses().ToList();
- }
-
- public void Start(SsdpHandler ssdpHandler)
- {
- _ssdpHandler = ssdpHandler;
- _ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived;
-
- foreach (var localIp in GetLocalIpAddresses())
- {
- try
- {
- CreateListener(localIp);
- }
- catch (Exception e)
- {
- _logger.ErrorException("Failed to Initilize Socket", e);
- }
- }
}
- async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
+ // Call this method from somewhere in your code to start the search.
+ public void BeginSearch()
{
- string nts;
- e.Headers.TryGetValue("NTS", out nts);
-
- if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) &&
- String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) &&
- !_disposed)
- {
- EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger);
- return;
- }
-
- try
- {
- if (e.LocalEndPoint == null)
- {
- var ip = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i));
- if (ip != null)
- {
- e.LocalEndPoint = new IPEndPoint(ip, 0);
- }
- }
-
- if (e.LocalEndPoint != null)
- {
- TryCreateDevice(e);
- }
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating play to controller", ex);
- }
- }
-
- private void CreateListener(IPAddress localIp)
- {
- Task.Factory.StartNew(async (o) =>
- {
- try
- {
- _logger.Info("Creating SSDP listener on {0}", localIp);
-
- var endPoint = new IPEndPoint(localIp, 1900);
-
- using (var socket = GetMulticastSocket(localIp, endPoint))
- {
- var receiveBuffer = new byte[64000];
-
- CreateNotifier(localIp);
-
- while (!_tokenSource.IsCancellationRequested)
- {
- var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
-
- if (receivedBytes > 0)
- {
- var args = SsdpHelper.ParseSsdpResponse(receiveBuffer);
- args.EndPoint = endPoint;
- args.LocalEndPoint = new IPEndPoint(localIp, 0);
-
- _ssdpHandler.LogMessageReceived(args, true);
-
- TryCreateDevice(args);
- }
- }
- }
-
- _logger.Info("SSDP listener - Task completed");
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception e)
- {
- _logger.ErrorException("Error in listener", e);
- }
-
- }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+ _DeviceLocator = new SsdpDeviceLocator();
+
+ // (Optional) Set the filter so we only see notifications for devices we care about
+ // (can be any search target value i.e device type, uuid value etc - any value that appears in the
+ // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
+ //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
+
+ // Connect our event handler so we process devices as they are found
+ _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
+ _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
+ // Enable listening for notifications (optional)
+ _DeviceLocator.StartListeningForNotifications();
+
+ // Perform a search so we don't have to wait for devices to broadcast notifications
+ // again to get any results right away (notifications are broadcast periodically).
+ StartAsyncSearch();
}
- private void CreateNotifier(IPAddress localIp)
+ private void StartAsyncSearch()
{
Task.Factory.StartNew(async (o) =>
{
@@ -153,7 +66,7 @@ namespace MediaBrowser.Dlna.Ssdp
{
while (true)
{
- _ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900));
+ await _DeviceLocator.SearchAsync().ConfigureAwait(false);
var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
@@ -165,66 +78,60 @@ namespace MediaBrowser.Dlna.Ssdp
}
catch (Exception ex)
{
- _logger.ErrorException("Error in notifier", ex);
+ _logger.ErrorException("Error searching for devices", ex);
}
- }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+ }, CancellationToken.None, TaskCreationOptions.LongRunning);
}
- private Socket GetMulticastSocket(IPAddress localIpAddress, EndPoint localEndpoint)
+ // Process each found device in the event handler
+ void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e)
{
- var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress));
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
+ var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
- socket.Bind(localEndpoint);
+ var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
- return socket;
- }
-
- private void TryCreateDevice(SsdpMessageEventArgs args)
- {
- string nts;
- args.Headers.TryGetValue("NTS", out nts);
+ var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
- if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
+ var args = new GenericEventArgs<UpnpDeviceInfo>
{
- if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase))
+ Argument = new UpnpDeviceInfo
{
- if (!_disposed)
- {
- EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
- }
+ Location = e.DiscoveredDevice.DescriptionLocation,
+ Headers = headers
}
+ };
- return;
- }
+ EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
+ }
- string usn;
- if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+ private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
+ {
+ var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
+
+ var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
- string nt;
- if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+ var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
- // Need to be able to download device description
- string location;
- if (!args.Headers.TryGetValue("Location", out location) ||
- string.IsNullOrEmpty(location))
+ var args = new GenericEventArgs<UpnpDeviceInfo>
{
- return;
- }
+ Argument = new UpnpDeviceInfo
+ {
+ Location = e.DiscoveredDevice.DescriptionLocation,
+ Headers = headers
+ }
+ };
- EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
+ EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
}
- public void Dispose()
+ public void Start()
{
- if (_ssdpHandler != null)
- {
- _ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived;
- }
+ BeginSearch();
+ }
+ public void Dispose()
+ {
if (!_disposed)
{
_disposed = true;
diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
index 720ea71a0..0d0ca98a2 100644
--- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
+++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
@@ -83,90 +83,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- public event EventHandler<SsdpMessageEventArgs> MessageReceived;
-
- private async void OnMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
- {
- if (IgnoreMessage(args, isMulticast))
- {
- return;
- }
-
- LogMessageReceived(args, isMulticast);
-
- var headers = args.Headers;
- string st;
-
- if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase) && headers.TryGetValue("st", out st))
- {
- TimeSpan delay = GetSearchDelay(headers);
-
- if (_config.GetDlnaConfiguration().EnableDebugLog)
- {
- _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds);
- }
-
- await Task.Delay(delay).ConfigureAwait(false);
-
- RespondToSearch(args.EndPoint, st);
- }
-
- EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
- }
-
- internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
- {
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- var headerText = string.Join(",", headerTexts.ToArray());
-
- var protocol = isMulticast ? "Multicast" : "Unicast";
- var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
- }
- }
-
- internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast)
- {
- string usn;
- if (args.Headers.TryGetValue("USN", out usn))
- {
- // USN=uuid:b67df29b5c379445fde78c3774ab518d::urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
- if (RegisteredDevices.Any(i => string.Equals(i.USN, usn, StringComparison.OrdinalIgnoreCase)))
- {
- //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- //var headerText = string.Join(",", headerTexts.ToArray());
-
- //var protocol = isMulticast ? "Multicast" : "Unicast";
- //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
- return true;
- }
- }
-
- string serverId;
- if (args.Headers.TryGetValue("X-EMBY-SERVERID", out serverId))
- {
- if (string.Equals(serverId, _appHost.SystemId, StringComparison.OrdinalIgnoreCase))
- {
- //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- //var headerText = string.Join(",", headerTexts.ToArray());
-
- //var protocol = isMulticast ? "Multicast" : "Unicast";
- //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
- return true;
- }
- }
-
- return false;
- }
-
public IEnumerable<UpnpDevice> RegisteredDevices
{
get
@@ -188,8 +104,6 @@ namespace MediaBrowser.Dlna.Ssdp
RestartSocketListener();
ReloadAliveNotifier();
- CreateUnicastClient();
-
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
@@ -202,32 +116,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- public void SendSearchMessage(EndPoint localIp)
- {
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- values["HOST"] = "239.255.255.250:1900";
- values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
- values["X-EMBY-SERVERID"] = _appHost.SystemId;
-
- values["MAN"] = "\"ssdp:discover\"";
-
- // Search target
- values["ST"] = "ssdp:all";
-
- // Seconds to delay response
- values["MX"] = "3";
-
- var header = "M-SEARCH * HTTP/1.1";
-
- var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
- // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
- SendDatagram(msg, _ssdpEndp, localIp, true);
-
- SendUnicastRequest(msg);
- }
-
public async void SendDatagram(string msg,
EndPoint endpoint,
EndPoint localAddress,
@@ -248,75 +136,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- /// <summary>
- /// According to the spec: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf
- /// Device responses should be delayed a random duration between 0 and this many seconds to balance
- /// load for the control point when it processes responses. In my testing kodi times out after mx
- /// so we will generate from mx - 1
- /// </summary>
- /// <param name="headers">The mx headers</param>
- /// <returns>A timepsan for the amount to delay before returning search result.</returns>
- private TimeSpan GetSearchDelay(Dictionary<string, string> headers)
- {
- string mx;
- headers.TryGetValue("mx", out mx);
- int delaySeconds = 0;
- if (!string.IsNullOrWhiteSpace(mx)
- && int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds)
- && delaySeconds > 1)
- {
- delaySeconds = new Random().Next(delaySeconds - 1);
- }
-
- return TimeSpan.FromSeconds(delaySeconds);
- }
-
- private void RespondToSearch(EndPoint endpoint, string deviceType)
- {
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- var isLogged = false;
-
- const string header = "HTTP/1.1 200 OK";
-
- foreach (var d in RegisteredDevices)
- {
- if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
- {
- if (!isLogged)
- {
- if (enableDebugLogging)
- {
- _logger.Debug("Responding to search from {0} for {1}", endpoint, deviceType);
- }
- isLogged = true;
- }
-
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- values["CACHE-CONTROL"] = "max-age = 600";
- values["DATE"] = DateTime.Now.ToString("R");
- values["EXT"] = "";
- values["LOCATION"] = d.Descriptor.ToString();
- values["SERVER"] = _serverSignature;
- values["ST"] = d.Type;
- values["USN"] = d.USN;
-
- var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
- SendDatagram(msg, endpoint, null, false, 2);
- SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2);
- //SendDatagram(header, values, endpoint, null, true);
-
- if (enableDebugLogging)
- {
- _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
- }
- }
- }
- }
-
private void RestartSocketListener()
{
if (_isDisposed)
@@ -329,8 +148,6 @@ namespace MediaBrowser.Dlna.Ssdp
_multicastSocket = CreateMulticastSocket();
_logger.Info("MultiCast socket created");
-
- Receive();
}
catch (Exception ex)
{
@@ -339,74 +156,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- private void Receive()
- {
- try
- {
- var buffer = new byte[1024];
-
- EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
- _multicastSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
- }
- catch (ObjectDisposedException)
- {
- if (!_isDisposed)
- {
- //StartSocketRetryTimer();
- }
- }
- catch (Exception ex)
- {
- _logger.Debug("Error in BeginReceiveFrom", ex);
- }
- }
-
- private void ReceiveCallback(IAsyncResult result)
- {
- if (_isDisposed)
- {
- return;
- }
-
- try
- {
- EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
- var length = _multicastSocket.EndReceiveFrom(result, ref endpoint);
-
- var received = (byte[])result.AsyncState;
-
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- _logger.Debug(Encoding.ASCII.GetString(received));
- }
-
- var args = SsdpHelper.ParseSsdpResponse(received);
- args.EndPoint = endpoint;
-
- OnMessageReceived(args, true);
- }
- catch (ObjectDisposedException)
- {
- if (!_isDisposed)
- {
- //StartSocketRetryTimer();
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Failed to read SSDP message", ex);
- }
-
- if (_multicastSocket != null)
- {
- Receive();
- }
- }
-
public void Dispose()
{
_config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
@@ -414,7 +163,6 @@ namespace MediaBrowser.Dlna.Ssdp
_isDisposed = true;
- DisposeUnicastClient();
DisposeSocket();
StopAliveNotifier();
}
@@ -523,137 +271,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- private void CreateUnicastClient()
- {
- if (_unicastClient == null)
- {
- try
- {
- _unicastClient = new UdpClient(_unicastPort);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating unicast client", ex);
- }
-
- UnicastSetBeginReceive();
- }
- }
-
- private void DisposeUnicastClient()
- {
- if (_unicastClient != null)
- {
- try
- {
- _unicastClient.Close();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error closing unicast client", ex);
- }
-
- _unicastClient = null;
- }
- }
-
- /// <summary>
- /// Listen for Unicast SSDP Responses
- /// </summary>
- private void UnicastSetBeginReceive()
- {
- try
- {
- var ipRxEnd = new IPEndPoint(IPAddress.Any, _unicastPort);
- var udpListener = new UdpState { EndPoint = ipRxEnd };
-
- udpListener.UdpClient = _unicastClient;
- _unicastClient.BeginReceive(UnicastReceiveCallback, udpListener);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in UnicastSetBeginReceive", ex);
- }
- }
-
- /// <summary>
- /// The UnicastReceiveCallback receives Http Responses
- /// and Fired the SatIpDeviceFound Event for adding the SatIpDevice
- /// </summary>
- /// <param name="ar"></param>
- private void UnicastReceiveCallback(IAsyncResult ar)
- {
- var udpClient = ((UdpState)(ar.AsyncState)).UdpClient;
- var endpoint = ((UdpState)(ar.AsyncState)).EndPoint;
- if (udpClient.Client != null)
- {
- try
- {
- var responseBytes = udpClient.EndReceive(ar, ref endpoint);
- var args = SsdpHelper.ParseSsdpResponse(responseBytes);
-
- args.EndPoint = endpoint;
-
- OnMessageReceived(args, false);
-
- UnicastSetBeginReceive();
- }
- catch (ObjectDisposedException)
- {
-
- }
- catch (SocketException)
- {
-
- }
- catch (Exception)
- {
- // If called while shutting down, seeing a NullReferenceException inside EndReceive
- }
- }
- }
-
- private void SendUnicastRequest(string request, int sendCount = 3)
- {
- if (_unicastClient == null)
- {
- return;
- }
-
- var ipSsdp = IPAddress.Parse(SSDPAddr);
- var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
-
- SendUnicastRequest(request, ipTxEnd, sendCount);
- }
-
- private async void SendUnicastRequest(string request, IPEndPoint toEndPoint, int sendCount = 3)
- {
- if (_unicastClient == null)
- {
- return;
- }
-
- //_logger.Debug("Sending unicast request");
-
- byte[] req = Encoding.ASCII.GetBytes(request);
-
- try
- {
- for (var i = 0; i < sendCount; i++)
- {
- if (i > 0)
- {
- await Task.Delay(50).ConfigureAwait(false);
- }
- _unicastClient.Send(req, req.Length, toEndPoint);
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in SendUnicastRequest", ex);
- }
- }
-
private readonly object _notificationTimerSyncLock = new object();
private int _aliveNotifierIntervalMs;
private void ReloadAliveNotifier()
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 351740e6e..ad7dea0a5 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -175,9 +175,6 @@
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
<Link>Configuration\AccessSchedule.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
- <Link>Configuration\AutoOnOff.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index ad3811646..61f2f3f13 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -147,9 +147,6 @@
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
<Link>Configuration\AccessSchedule.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
- <Link>Configuration\AutoOnOff.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
@@ -1193,4 +1190,4 @@
<Target Name="AfterBuild">
</Target>
-->
-</Project>
+</Project> \ No newline at end of file
diff --git a/MediaBrowser.Model/Configuration/AutoOnOff.cs b/MediaBrowser.Model/Configuration/AutoOnOff.cs
deleted file mode 100644
index e911a0ff1..000000000
--- a/MediaBrowser.Model/Configuration/AutoOnOff.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-
-namespace MediaBrowser.Model.Configuration
-{
- public enum AutoOnOff
- {
- Auto,
- Enabled,
- Disabled
- }
-}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 40ac4be8a..5cf266674 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -183,8 +183,6 @@ namespace MediaBrowser.Model.Configuration
public int RemoteClientBitrateLimit { get; set; }
- public AutoOnOff EnableLibraryMonitor { get; set; }
-
public int SharingExpirationDays { get; set; }
public string[] Migrations { get; set; }
@@ -244,7 +242,6 @@ namespace MediaBrowser.Model.Configuration
// 5 minutes
MinResumeDurationSeconds = 300;
- EnableLibraryMonitor = AutoOnOff.Auto;
LibraryMonitorDelay = 60;
EnableInternetProviders = true;
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index db70b8606..c1a01680d 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -89,7 +89,6 @@
<Compile Include="Chapters\RemoteChapterResult.cs" />
<Compile Include="Collections\CollectionCreationResult.cs" />
<Compile Include="Configuration\AccessSchedule.cs" />
- <Compile Include="Configuration\AutoOnOff.cs" />
<Compile Include="Configuration\ChannelOptions.cs" />
<Compile Include="Configuration\ChapterOptions.cs" />
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 280bec65b..1021d8823 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Net;
using MediaBrowser.Common.Threading;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
- private readonly ISsdpHandler _ssdp;
+ private readonly IDeviceDiscovery _deviceDiscovery;
private PeriodicTimer _timer;
private bool _isStarted;
- public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
+ public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery)
{
_logger = logmanager.GetLogger("PortMapper");
_appHost = appHost;
_config = config;
- _ssdp = ssdp;
+ _deviceDiscovery = deviceDiscovery;
}
private string _lastConfigIdentifier;
@@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
public void Run()
{
- //NatUtility.Logger = new LogWriter(_logger);
+ NatUtility.Logger = _logger;
if (_config.Configuration.EnableUPnP)
{
@@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
- _ssdp.MessageReceived += _ssdp_MessageReceived;
+ _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
_isStarted = true;
}
- private void ClearCreatedRules(object state)
- {
- _createdRules = new List<string>();
- _usnsHandled = new List<string>();
- }
-
- void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
+ private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
- var endpoint = e.EndPoint as IPEndPoint;
-
- if (endpoint == null || e.LocalEndPoint == null)
- {
- return;
- }
+ var info = e.Argument;
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
// Filter device type
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
- if (!_usnsHandled.Contains(identifier))
+ if (info.Location != null && !_usnsHandled.Contains(identifier))
{
_usnsHandled.Add(identifier);
_logger.Debug("Calling Nat.Handle on " + identifier);
- NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
+
+ IPAddress address;
+ if (IPAddress.TryParse(info.Location.Host, out address))
+ {
+ // The Handle method doesn't need the port
+ var endpoint = new IPEndPoint(address, info.Location.Port);
+
+ IPAddress localAddress = null;
+
+ try
+ {
+ var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
+
+ if (!IPAddress.TryParse(localAddressString, out localAddress))
+ {
+ return;
+ }
+ }
+ catch
+ {
+ return;
+ }
+
+ NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp);
+ }
}
}
+ private void ClearCreatedRules(object state)
+ {
+ _createdRules = new List<string>();
+ _usnsHandled = new List<string>();
+ }
+
void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var ex = e.ExceptionObject as Exception;
@@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = null;
}
- _ssdp.MessageReceived -= _ssdp_MessageReceived;
+ _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
try
{
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index c87d10ef4..80364bb55 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -164,32 +164,16 @@ namespace MediaBrowser.Server.Implementations.IO
Start();
}
- private bool EnableLibraryMonitor
- {
- get
- {
- switch (ConfigurationManager.Configuration.EnableLibraryMonitor)
- {
- case AutoOnOff.Auto:
- return Environment.OSVersion.Platform == PlatformID.Win32NT;
- case AutoOnOff.Enabled:
- return true;
- default:
- return false;
- }
- }
- }
-
private bool IsLibraryMonitorEnabaled(BaseItem item)
{
var options = LibraryManager.GetLibraryOptions(item);
- if (options != null && options.SchemaVersion >= 1)
+ if (options != null)
{
return options.EnableRealtimeMonitor;
}
- return EnableLibraryMonitor;
+ return false;
}
public void Start()
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 1a5ebedc2..6d2f79fa0 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -166,7 +166,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
if (imageIndex > -1)
{
- programDict[schedule.programID].images = GetProgramLogo(ApiUrl, images[imageIndex]);
+ var programEntry = programDict[schedule.programID];
+
+ var data = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
+ data = data.OrderByDescending(GetSizeOrder).ToList();
+
+ programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true);
+ //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false);
+ //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-LOT", false);
+
+ if (!string.IsNullOrWhiteSpace(programEntry.thumbImage))
+ {
+ var b = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(programEntry.bannerImage))
+ {
+ var b = true;
+ }
}
}
@@ -179,6 +199,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return programsInfo;
}
+ private int GetSizeOrder(ScheduleDirect.ImageData image)
+ {
+ if (!string.IsNullOrWhiteSpace(image.size))
+ {
+ int value;
+ if (int.TryParse(image.size, out value))
+ {
+ return value;
+ }
+ }
+
+ return 0;
+ }
+
private readonly object _channelCacheLock = new object();
private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName)
{
@@ -384,13 +418,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
episodeTitle = details.episodeTitle150;
}
- string imageUrl = null;
-
- if (details.hasImageArtwork)
- {
- imageUrl = details.images;
- }
-
var showType = details.showType ?? string.Empty;
var info = new ProgramInfo
@@ -406,7 +433,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
Audio = audioType,
IsRepeat = repeat,
IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1,
- ImageUrl = imageUrl,
+ ImageUrl = details.primaryImage,
IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1,
IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", StringComparison.OrdinalIgnoreCase) != -1,
@@ -485,36 +512,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return date;
}
- private string GetProgramLogo(string apiUrl, ScheduleDirect.ShowImages images)
+ private string GetProgramImage(string apiUrl, List<ScheduleDirect.ImageData> images, string category, bool returnDefaultImage)
{
string url = null;
- if (images.data != null)
+
+ var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase));
+ if (logoIndex == -1)
{
- var smallImages = images.data.Where(i => i.size == "Sm").ToList();
- if (smallImages.Any())
+ if (!returnDefaultImage)
{
- images.data = smallImages;
+ return null;
}
- var logoIndex = images.data.FindIndex(i => i.category == "Logo");
- if (logoIndex == -1)
+ logoIndex = 0;
+ }
+ var uri = images[logoIndex].uri;
+
+ if (!string.IsNullOrWhiteSpace(uri))
+ {
+ if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
{
- logoIndex = 0;
+ url = uri;
}
- var uri = images.data[logoIndex].uri;
-
- if (!string.IsNullOrWhiteSpace(uri))
+ else
{
- if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
- {
- url = uri;
- }
- else
- {
- url = apiUrl + "/image/" + uri;
- }
+ url = apiUrl + "/image/" + uri;
}
- //_logger.Debug("URL for image is : " + url);
}
+ //_logger.Debug("URL for image is : " + url);
return url;
}
@@ -1204,7 +1228,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
public List<Crew> crew { get; set; }
public string showType { get; set; }
public bool hasImageArtwork { get; set; }
- public string images { get; set; }
+ public string primaryImage { get; set; }
+ public string thumbImage { get; set; }
+ public string bannerImage { get; set; }
public string imageID { get; set; }
public string md5 { get; set; }
public List<string> contentAdvisory { get; set; }
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
index 9ba1c60cc..ef37e3b35 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
@@ -10,6 +10,7 @@ using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
string server = null;
- if (e.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
+ var info = e.Argument;
+
+ if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
{
string location;
- if (e.Headers.TryGetValue("Location", out location))
+ if (info.Headers.TryGetValue("Location", out location))
{
//_logger.Debug("HdHomerun found at {0}", location);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
index cb0e573da..a0b8ef5f7 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Extensions;
using System.Xml.Linq;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -50,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string st = null;
string nt = null;
- e.Headers.TryGetValue("ST", out st);
- e.Headers.TryGetValue("NT", out nt);
+ info.Headers.TryGetValue("ST", out st);
+ info.Headers.TryGetValue("NT", out nt);
if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
{
string location;
- if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
+ if (info.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
{
_logger.Debug("SAT IP found at {0}", location);
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 8850f3d35..e182ad6a5 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -105,9 +105,6 @@
<Reference Include="UniversalDetector">
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
</Reference>
- <Reference Include="Mono.Nat">
- <HintPath>..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll</HintPath>
- </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -390,6 +387,10 @@
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
+ <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
+ <Project>{d7453b88-2266-4805-b39b-2b5a2a33e1ba}</Project>
+ <Name>Mono.Nat</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Localization\Ratings\us.txt" />
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 746dc7f62..94522cd50 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -5,7 +5,6 @@
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.55" targetFramework="net45" />
- <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index bcbb10174..e7acb3f50 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
@@ -10,8 +10,9 @@
<RootNamespace>MediaBrowser.Server.Mono</RootNamespace>
<AssemblyName>MediaBrowser.Server.Mono</AssemblyName>
<StartupObject>MediaBrowser.Server.Mono.MainClass</StartupObject>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config
index b0e8558fd..e14b908ad 100644
--- a/MediaBrowser.Server.Mono/app.config
+++ b/MediaBrowser.Server.Mono/app.config
@@ -1,21 +1,21 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
- <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
+ <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true"></targets>
</nlog>
<appSettings>
- <add key="DebugProgramDataPath" value="ProgramData-Server" />
- <add key="ReleaseProgramDataPath" value="ProgramData-Server" />
+ <add key="DebugProgramDataPath" value="ProgramData-Server"/>
+ <add key="ReleaseProgramDataPath" value="ProgramData-Server"/>
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
- <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
+ <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
-</configuration>
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
index 433855ea0..e6d9b482e 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
@@ -549,7 +549,7 @@ namespace MediaBrowser.Server.Startup.Common
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
RegisterSingleInstance(SubtitleManager);
- RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, this, NetworkManager));
+ RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager));
ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
RegisterSingleInstance(ChapterManager);
@@ -566,8 +566,6 @@ namespace MediaBrowser.Server.Startup.Common
await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
- RegisterSingleInstance<ISsdpHandler>(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this));
-
var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false);
RegisterSingleInstance(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
index 778002e50..7eba89650 100644
--- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
+++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
@@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Server.Startup.Common</RootNamespace>
<AssemblyName>MediaBrowser.Server.Startup.Common</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 0b9dd90cd..7e0d834fa 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
+EndProject
Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
@@ -522,6 +524,36 @@ Global
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs
new file mode 100644
index 000000000..046cfc10f
--- /dev/null
+++ b/Mono.Nat/AbstractNatDevice.cs
@@ -0,0 +1,97 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// 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
+{
+ public abstract class AbstractNatDevice : INatDevice
+ {
+ private DateTime lastSeen;
+
+ protected AbstractNatDevice ()
+ {
+
+ }
+
+ public abstract IPAddress LocalAddress { get; }
+
+ public DateTime LastSeen
+ {
+ get { return lastSeen; }
+ 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);
+ }
+}
diff --git a/Mono.Nat/AsyncResults/AsyncResult.cs b/Mono.Nat/AsyncResults/AsyncResult.cs
new file mode 100644
index 000000000..e98e7d7ca
--- /dev/null
+++ b/Mono.Nat/AsyncResults/AsyncResult.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace Mono.Nat
+{
+ internal class AsyncResult : IAsyncResult
+ {
+ private object asyncState;
+ private AsyncCallback callback;
+ private bool completedSynchronously;
+ private bool isCompleted;
+ private Exception storedException;
+ private ManualResetEvent waitHandle;
+
+ public AsyncResult(AsyncCallback callback, object asyncState)
+ {
+ this.callback = callback;
+ this.asyncState = asyncState;
+ waitHandle = new ManualResetEvent(false);
+ }
+
+ public object AsyncState
+ {
+ get { return asyncState; }
+ }
+
+ public ManualResetEvent AsyncWaitHandle
+ {
+ get { return waitHandle; }
+ }
+
+ WaitHandle IAsyncResult.AsyncWaitHandle
+ {
+ get { return waitHandle; }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get { return completedSynchronously; }
+ protected internal set { completedSynchronously = value; }
+ }
+
+ public bool IsCompleted
+ {
+ get { return isCompleted; }
+ protected internal set { isCompleted = value; }
+ }
+
+ public Exception StoredException
+ {
+ get { return storedException; }
+ }
+
+ public void Complete()
+ {
+ Complete(storedException);
+ }
+
+ public void Complete(Exception ex)
+ {
+ storedException = ex;
+ isCompleted = true;
+ waitHandle.Set();
+
+ if (callback != null)
+ callback(this);
+ }
+ }
+}
diff --git a/Mono.Nat/Enums/MapState.cs b/Mono.Nat/Enums/MapState.cs
new file mode 100644
index 000000000..5ed2abd8f
--- /dev/null
+++ b/Mono.Nat/Enums/MapState.cs
@@ -0,0 +1,36 @@
+//
+// 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
+{
+ public enum MapState
+ {
+ AlreadyMapped,
+ Available
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs
new file mode 100644
index 000000000..a1f5cbb0e
--- /dev/null
+++ b/Mono.Nat/Enums/ProtocolType.cs
@@ -0,0 +1,36 @@
+//
+// 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
+{
+ public enum Protocol
+ {
+ Tcp,
+ Udp
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs
new file mode 100644
index 000000000..fbbbf63e3
--- /dev/null
+++ b/Mono.Nat/EventArgs/DeviceEventArgs.cs
@@ -0,0 +1,45 @@
+//
+// 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
+{
+ public class DeviceEventArgs : EventArgs
+ {
+ private INatDevice device;
+
+ public DeviceEventArgs(INatDevice device)
+ {
+ this.device = device;
+ }
+
+ public INatDevice Device
+ {
+ get { return this.device; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Exceptions/MappingException.cs b/Mono.Nat/Exceptions/MappingException.cs
new file mode 100644
index 000000000..bb2e6a69d
--- /dev/null
+++ b/Mono.Nat/Exceptions/MappingException.cs
@@ -0,0 +1,87 @@
+//
+// 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.Security.Permissions;
+
+namespace Mono.Nat
+{
+ [Serializable]
+ public class MappingException : Exception
+ {
+ private int errorCode;
+ private string errorText;
+
+ public int ErrorCode
+ {
+ get { return this.errorCode; }
+ }
+
+ public string ErrorText
+ {
+ get { return this.errorText; }
+ }
+
+ #region Constructors
+ public MappingException()
+ : base()
+ {
+ }
+
+ public MappingException(string message)
+ : base(message)
+ {
+ }
+
+ public MappingException(int errorCode, string errorText)
+ : base (string.Format ("Error {0}: {1}", errorCode, errorText))
+ {
+ this.errorCode = errorCode;
+ this.errorText = errorText;
+ }
+
+ public MappingException(string message, Exception innerException)
+ : 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
new file mode 100644
index 000000000..b18e6cff2
--- /dev/null
+++ b/Mono.Nat/IMapper.cs
@@ -0,0 +1,50 @@
+//
+// 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
new file mode 100644
index 000000000..c9f27055b
--- /dev/null
+++ b/Mono.Nat/INatDevice.cs
@@ -0,0 +1,62 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// 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
+{
+ public interface INatDevice
+ {
+ void CreatePortMap (Mapping mapping);
+ void DeletePortMap (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/ISearcher.cs b/Mono.Nat/ISearcher.cs
new file mode 100644
index 000000000..56e438105
--- /dev/null
+++ b/Mono.Nat/ISearcher.cs
@@ -0,0 +1,51 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net.Sockets;
+using System.Net;
+
+namespace Mono.Nat
+{
+ public delegate void NatDeviceCallback(INatDevice device);
+
+ internal interface ISearcher
+ {
+ event EventHandler<DeviceEventArgs> DeviceFound;
+ event EventHandler<DeviceEventArgs> DeviceLost;
+
+ void Search();
+ void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint);
+ DateTime NextSearch { get; }
+ NatProtocol Protocol { get; }
+ }
+}
diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs
new file mode 100644
index 000000000..dd49404c6
--- /dev/null
+++ b/Mono.Nat/Mapping.cs
@@ -0,0 +1,123 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// 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
+{
+ public class Mapping
+ {
+ private string description;
+ private DateTime expiration;
+ private int lifetime;
+ private int privatePort;
+ private Protocol protocol;
+ private int publicPort;
+
+
+
+ public Mapping (Protocol protocol, int privatePort, int publicPort)
+ : this (protocol, privatePort, publicPort, 0)
+ {
+ }
+
+ public Mapping (Protocol protocol, int privatePort, int publicPort, int lifetime)
+ {
+ this.protocol = protocol;
+ this.privatePort = privatePort;
+ this.publicPort = publicPort;
+ this.lifetime = lifetime;
+
+ if (lifetime == int.MaxValue)
+ this.expiration = DateTime.MaxValue;
+ else if (lifetime == 0)
+ this.expiration = DateTime.Now;
+ else
+ this.expiration = DateTime.Now.AddSeconds (lifetime);
+ }
+
+ public string Description
+ {
+ get { return description; }
+ set { description = value; }
+ }
+
+ public Protocol Protocol
+ {
+ get { return protocol; }
+ internal set { protocol = value; }
+ }
+
+ public int PrivatePort
+ {
+ get { return privatePort; }
+ internal set { privatePort = value; }
+ }
+
+ public int PublicPort
+ {
+ get { return publicPort; }
+ internal set { publicPort = value; }
+ }
+
+ public int Lifetime
+ {
+ get { return lifetime; }
+ internal set { lifetime = value; }
+ }
+
+ public DateTime Expiration
+ {
+ get { return expiration; }
+ internal set { expiration = value; }
+ }
+
+ public bool IsExpired ()
+ {
+ return expiration < DateTime.Now;
+ }
+
+ public override bool Equals (object obj)
+ {
+ Mapping other = obj as Mapping;
+ return other == null ? false : this.protocol == other.protocol &&
+ this.privatePort == other.privatePort && this.publicPort == other.publicPort;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode();
+ }
+
+ public override string ToString( )
+ {
+ return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}",
+ this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime );
+ }
+ }
+}
diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj
new file mode 100644
index 000000000..9c2781433
--- /dev/null
+++ b/Mono.Nat/Mono.Nat.csproj
@@ -0,0 +1,104 @@
+<?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/NatProtocol.cs b/Mono.Nat/NatProtocol.cs
new file mode 100644
index 000000000..ade8d921c
--- /dev/null
+++ b/Mono.Nat/NatProtocol.cs
@@ -0,0 +1,9 @@
+
+namespace Mono.Nat
+{
+ public enum NatProtocol
+ {
+ Upnp = 0,
+ Pmp = 1
+ }
+}
diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs
new file mode 100644
index 000000000..6d91d2513
--- /dev/null
+++ b/Mono.Nat/NatUtility.cs
@@ -0,0 +1,264 @@
+//
+// 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.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Linq;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Logging;
+using Mono.Nat.Pmp.Mappers;
+using Mono.Nat.Upnp.Mappers;
+
+namespace Mono.Nat
+{
+ public static class NatUtility
+ {
+ private static ManualResetEvent searching;
+ 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 bool Verbose
+ {
+ get { return verbose; }
+ set { verbose = value; }
+ }
+
+ static NatUtility()
+ {
+ 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 =>
+ {
+ searcher.DeviceFound += (sender, args) =>
+ {
+ if (DeviceFound != null)
+ DeviceFound(sender, args);
+ };
+ searcher.DeviceLost += (sender, args) =>
+ {
+ if (DeviceLost != null)
+ DeviceLost(sender, args);
+ };
+ });
+ Thread t = new Thread(SearchAndListen);
+ t.IsBackground = true;
+ t.Start();
+ }
+
+ internal static void Log(string format, params object[] args)
+ {
+ var logger = Logger;
+ if (logger != null)
+ logger.Debug(format, args);
+ }
+
+ private static void SearchAndListen()
+ {
+ while (true)
+ {
+ searching.WaitOne();
+
+ try
+ {
+ 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);
+ }
+
+ foreach (ISearcher s in controllers)
+ if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol))
+ {
+ Log("Searching for: {0}", s.GetType().Name);
+ s.Search();
+ }
+ }
+ catch (Exception e)
+ {
+ if (UnhandledException != null)
+ UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false));
+ }
+ Thread.Sleep(10);
+ }
+ }
+
+ static void 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);
+ 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 ()
+ {
+ searching.Set();
+ }
+
+ public static void StopDiscovery ()
+ {
+ 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)
+ {
+ byte[] ba = address.GetAddressBytes ();
+
+ switch ((int)ba[0]) {
+ case 10:
+ return true; //10.x.x.x
+ case 172:
+ return ((int)ba[1] & 16) != 0; //172.16-31.x.x
+ case 192:
+ return (int)ba[1] == 168; //192.168.x.x
+ default:
+ return false;
+ }
+ }
+
+ public static void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint, NatProtocol protocol)
+ {
+ switch (protocol)
+ {
+ case NatProtocol.Upnp:
+ UpnpSearcher.Instance.Handle(localAddress, response, endpoint);
+ break;
+ case NatProtocol.Pmp:
+ PmpSearcher.Instance.Handle(localAddress, response, endpoint);
+ break;
+ default:
+ throw new ArgumentException("Unexpected protocol: " + protocol);
+ }
+ }
+
+ public static void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol)
+ {
+ switch (protocol)
+ {
+ case NatProtocol.Upnp:
+ UpnpSearcher.Instance.Handle(localAddress, deviceInfo, endpoint);
+ break;
+ default:
+ throw new ArgumentException("Unexpected protocol: " + protocol);
+ }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
new file mode 100644
index 000000000..c8ccf5435
--- /dev/null
+++ b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
@@ -0,0 +1,52 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// 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.Pmp
+{
+ internal class PortMapAsyncResult : AsyncResult
+ {
+ private Mapping mapping;
+
+ internal PortMapAsyncResult (Mapping mapping, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.mapping = mapping;
+ }
+
+ internal PortMapAsyncResult (Protocol protocol, int port, int lifetime, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.mapping = new Mapping (protocol, port, port, lifetime);
+ }
+
+ internal Mapping Mapping
+ {
+ get { return mapping; }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/Mappers/PmpMapper.cs b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
new file mode 100644
index 000000000..f33ca44c3
--- /dev/null
+++ b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
@@ -0,0 +1,83 @@
+//
+// 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
new file mode 100644
index 000000000..6795561b1
--- /dev/null
+++ b/Mono.Nat/Pmp/Pmp.cs
@@ -0,0 +1,118 @@
+//
+// 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/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs
new file mode 100644
index 000000000..ff3eb6230
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpConstants.cs
@@ -0,0 +1,56 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// 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.Pmp
+{
+ internal static class PmpConstants
+ {
+ public const byte Version = (byte)0;
+
+ public const byte OperationCode = (byte)0;
+ public const byte OperationCodeUdp = (byte)1;
+ public const byte OperationCodeTcp = (byte)2;
+ public const byte ServerNoop = (byte)128;
+
+ public const int ClientPort = 5350;
+ public const int ServerPort = 5351;
+
+ public const int RetryDelay = 250;
+ public const int RetryAttempts = 9;
+
+ public const int RecommendedLeaseTime = 60 * 60;
+ public const int DefaultLeaseTime = RecommendedLeaseTime;
+
+ public const short ResultCodeSuccess = 0;
+ public const short ResultCodeUnsupportedVersion = 1;
+ public const short ResultCodeNotAuthorized = 2;
+ public const short ResultCodeNetworkFailure = 3;
+ public const short ResultCodeOutOfResources = 4;
+ public const short ResultCodeUnsupportedOperationCode = 5;
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs
new file mode 100644
index 000000000..9a2962c4d
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpNatDevice.cs
@@ -0,0 +1,347 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// 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.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Collections.Generic;
+
+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; }
+ }
+
+ 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 ();
+ }
+
+ public override IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState)
+ {
+ StartOp(ref externalIpResult, callback, asyncState);
+ AsyncResult result = externalIpResult;
+ result.Complete();
+ return result;
+ }
+
+ 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 IPAddress EndGetExternalIP (IAsyncResult result)
+ {
+ EndOp(result, ref externalIpResult);
+ return publicAddress;
+ }
+
+ private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState)
+ {
+ if (pendingOp == true)
+ throw new InvalidOperationException("Can only have one simultaenous async operation");
+
+ pendingOp = true;
+ result = new AsyncResult(callback, asyncState);
+ }
+
+ private void EndOp(IAsyncResult supplied, ref AsyncResult actual)
+ {
+ if (supplied == null)
+ throw new ArgumentNullException("result");
+
+ if (supplied != actual)
+ throw new ArgumentException("Supplied IAsyncResult does not match the stored result");
+
+ if (!supplied.IsCompleted)
+ supplied.AsyncWaitHandle.WaitOne();
+
+ if (actual.StoredException != null)
+ throw actual.StoredException;
+
+ pendingOp = false;
+ actual = null;
+ }
+
+ 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();
+ }
+
+ public bool Equals (PmpNatDevice other)
+ {
+ return (other == null) ? false : this.publicAddress.Equals(other.publicAddress);
+ }
+
+ 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)));
+
+ CreatePortMapAsyncState state = new CreatePortMapAsyncState ();
+ state.Buffer = package.ToArray ();
+ state.Mapping = mapping;
+
+ 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);
+
+ int attempt = 0;
+ int delay = PmpConstants.RetryDelay;
+
+ ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState);
+
+ while (attempt < PmpConstants.RetryAttempts && !listenState.Success) {
+ udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort));
+ listenState.UdpClientReady.Set();
+
+ 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;
+
+ 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;
+ }
+
+ catch (ObjectDisposedException)
+ {
+ state.Success = false;
+ 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;
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// Overridden.
+ /// </summary>
+ /// <returns></returns>
+ public override string ToString( )
+ {
+ 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
new file mode 100644
index 000000000..df0273ccb
--- /dev/null
+++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs
@@ -0,0 +1,149 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Pmp;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Linq;
+
+namespace Mono.Nat
+{
+ internal class PmpSearcher : Pmp.Pmp, ISearcher
+ {
+ static PmpSearcher instance = new PmpSearcher();
+
+
+ public static PmpSearcher Instance
+ {
+ get { return instance; }
+ }
+
+ private int timeout;
+ private DateTime nextSearch;
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+ public event EventHandler<DeviceEventArgs> DeviceLost;
+
+ static PmpSearcher()
+ {
+ CreateSocketsAndAddGateways();
+ }
+
+ PmpSearcher()
+ {
+ timeout = 250;
+ }
+
+ public void Search()
+ {
+ foreach (UdpClient s in sockets)
+ {
+ try
+ {
+ Search(s);
+ }
+ catch
+ {
+ // Ignore any search errors
+ }
+ }
+ }
+
+ void 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
+ // (and that attempt fails), assume no devices available
+ nextSearch = DateTime.Now.AddMilliseconds(timeout);
+ timeout *= 2;
+
+ // We've tried 9 times as per spec, try searching again in 5 minutes
+ if (timeout == 128 * 1000)
+ {
+ timeout = 250;
+ nextSearch = DateTime.Now.AddMinutes(10);
+ return;
+ }
+
+ // 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);
+ }
+
+ bool IsSearchAddress(IPAddress address)
+ {
+ foreach (List<IPEndPoint> gatewayList in gatewayLists.Values)
+ foreach (IPEndPoint gatewayEndpoint in gatewayList)
+ if (gatewayEndpoint.Address.Equals(address))
+ return true;
+ return false;
+ }
+
+ public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ 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] });
+ nextSearch = DateTime.Now.AddMinutes(5);
+ timeout = 250;
+ OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp)));
+ }
+
+ public DateTime NextSearch
+ {
+ get { return nextSearch; }
+ }
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+
+ public NatProtocol Protocol
+ {
+ get { return NatProtocol.Pmp; }
+ }
+ }
+}
diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..c3c3101de
--- /dev/null
+++ b/Mono.Nat/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 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
+// 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
new file mode 100644
index 000000000..51ecfbaf0
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
@@ -0,0 +1,56 @@
+//
+// 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
new file mode 100644
index 000000000..d8ac3fe61
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
@@ -0,0 +1,75 @@
+//
+// 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
new file mode 100644
index 000000000..6f2716805
--- /dev/null
+++ b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs
@@ -0,0 +1,110 @@
+//
+// 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/DiscoverDeviceMessage.cs b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
new file mode 100644
index 000000000..87f5835a6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
@@ -0,0 +1,60 @@
+//
+// 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.Text;
+
+namespace Mono.Nat.Upnp
+{
+ internal static class DiscoverDeviceMessage
+ {
+ /// <summary>
+ /// The message sent to discover all uPnP devices on the network
+ /// </summary>
+ /// <returns></returns>
+ public static byte[] EncodeSSDP()
+ {
+ string s = "M-SEARCH * HTTP/1.1\r\n"
+ + "HOST: 239.255.255.250:1900\r\n"
+ + "MAN: \"ssdp:discover\"\r\n"
+ + "MX: 3\r\n"
+ + "ST: ssdp:all\r\n\r\n";
+ return UTF8Encoding.ASCII.GetBytes(s);
+ }
+
+ public static byte[] EncodeUnicast(IPAddress gatewayAddress)
+ {
+ //Format obtained from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf pg 31
+ //This method only works with upnp 1.1 routers... unfortunately
+ string s = "M-SEARCH * HTTP/1.1\r\n"
+ + "HOST: " + gatewayAddress + ":1900\r\n"
+ + "MAN: \"ssdp:discover\"\r\n"
+ + "ST: ssdp:all\r\n\r\n";
+ //+ "USER-AGENT: unix/5.1 UPnP/1.1 MyProduct/1.0\r\n\r\n";
+ return UTF8Encoding.ASCII.GetBytes(s);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
new file mode 100644
index 000000000..ce5270e9b
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
@@ -0,0 +1,63 @@
+//
+// 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 ErrorMessage : MessageBase
+ {
+ #region Member Variables
+ public string Description
+ {
+ get { return this.description; }
+ }
+ private string description;
+
+ public int ErrorCode
+ {
+ get { return this.errorCode; }
+ }
+ private int errorCode;
+ #endregion
+
+
+ #region Constructors
+ public ErrorMessage(int errorCode, string description)
+ :base(null)
+ {
+ this.description = description;
+ this.errorCode = errorCode;
+ }
+ #endregion
+
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
new file mode 100644
index 000000000..c5d7bce70
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
@@ -0,0 +1,62 @@
+//
+// 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.Diagnostics;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetServicesMessage : MessageBase
+ {
+ private string servicesDescriptionUrl;
+ private EndPoint hostAddress;
+
+ public GetServicesMessage(string description, EndPoint hostAddress)
+ :base(null)
+ {
+ if (string.IsNullOrEmpty(description))
+ Trace.WriteLine("Description is null");
+
+ if (hostAddress == null)
+ Trace.WriteLine("hostaddress is null");
+
+ this.servicesDescriptionUrl = description;
+ this.hostAddress = hostAddress;
+ }
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl);
+ req.Headers.Add("ACCEPT-LANGUAGE", "en");
+ req.Method = "GET";
+
+ body = new byte[0];
+ return req;
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
new file mode 100644
index 000000000..da650fb41
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
@@ -0,0 +1,75 @@
+//
+// 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.Globalization;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class CreatePortMappingMessage : MessageBase
+ {
+ #region Private Fields
+
+ private IPAddress localIpAddress;
+ private Mapping mapping;
+
+ #endregion
+
+
+ #region Constructors
+ public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device)
+ : base(device)
+ {
+ this.mapping = mapping;
+ this.localIpAddress = localIpAddress;
+ }
+ #endregion
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ 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(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
new file mode 100644
index 000000000..d9be89a69
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
@@ -0,0 +1,57 @@
+//
+// 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
new file mode 100644
index 000000000..8f97002ea
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
@@ -0,0 +1,51 @@
+//
+// 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
new file mode 100644
index 000000000..c0c555881
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
@@ -0,0 +1,55 @@
+//
+// 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
new file mode 100644
index 000000000..314468ece
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
@@ -0,0 +1,60 @@
+//
+// 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
new file mode 100644
index 000000000..e75926b09
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
@@ -0,0 +1,46 @@
+//
+// 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 CreatePortMappingResponseMessage : MessageBase
+ {
+ #region Constructors
+ public CreatePortMappingResponseMessage()
+ :base(null)
+ {
+ }
+ #endregion
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
new file mode 100644
index 000000000..1fce4eb04
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
@@ -0,0 +1,44 @@
+//
+// 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
new file mode 100644
index 000000000..ee4b18cd1
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
@@ -0,0 +1,53 @@
+//
+// 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
new file mode 100644
index 000000000..b11bfa027
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
@@ -0,0 +1,108 @@
+//
+// 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
new file mode 100644
index 000000000..44c16eec6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
@@ -0,0 +1,132 @@
+//
+// 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.Diagnostics;
+using System.Xml;
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Globalization;
+
+namespace Mono.Nat.Upnp
+{
+ internal abstract class MessageBase
+ {
+ internal static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
+ protected UpnpNatDevice device;
+
+ protected MessageBase(UpnpNatDevice device)
+ {
+ this.device = device;
+ }
+
+ protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body)
+ {
+ 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 + "\"");
+
+ 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";
+
+ body = 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();
+
+ 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 abstract WebRequest Encode(out byte[] body);
+
+ internal static void WriteFullElement(XmlWriter writer, string element, string value)
+ {
+ writer.WriteStartElement(element);
+ writer.WriteString(value);
+ writer.WriteEndElement();
+ }
+
+ internal static XmlWriter CreateWriter(StringBuilder sb)
+ {
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.ConformanceLevel = ConformanceLevel.Fragment;
+ return XmlWriter.Create(sb, settings);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
new file mode 100644
index 000000000..edc5a5d76
--- /dev/null
+++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
@@ -0,0 +1,287 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Upnp;
+using System.Diagnostics;
+using System.Net.Sockets;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.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;
+
+ UpnpSearcher()
+ {
+ 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);
+ }
+
+ 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
+ // we should still be in a position to handle the next reply correctly.
+ try
+ {
+ /* 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. */
+
+ // We have an internet gateway device now
+ UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty);
+
+ 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)
+ {
+ NatUtility.Log("Unhandled exception when trying to decode a device's response Send me the following data: ");
+ NatUtility.Log("ErrorMessage:");
+ NatUtility.Log(ex.Message);
+ }
+ }
+
+ 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
+ {
+ 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)
+ DeviceFound(this, args);
+ }
+
+ public NatProtocol Protocol
+ {
+ get { return NatProtocol.Upnp; }
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs
new file mode 100644
index 000000000..e44a51c24
--- /dev/null
+++ b/Mono.Nat/Upnp/Upnp.cs
@@ -0,0 +1,83 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Mono.Nat.Upnp
+{
+ internal class Upnp
+ {
+ public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ // Convert it to a string for easy parsing
+ string dataString = null;
+
+
+ string urn;
+ dataString = Encoding.UTF8.GetString(response);
+
+ if (NatUtility.Verbose)
+ NatUtility.Log("UPnP Response: {0}", dataString);
+
+ /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
+ Any other device type is no good to us for this purpose. See the IGP overview paper
+ page 5 for an overview of device types and their hierarchy.
+ http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+ version it is and apply the correct URN. */
+
+ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+ prefix. */
+
+ string log = "UPnP Response: Router advertised a '{0}' service";
+ StringComparison c = StringComparison.OrdinalIgnoreCase;
+ if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1)
+ {
+ urn = "urn:schemas-upnp-org:service:WANIPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1");
+ }
+ else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1)
+ {
+ urn = "urn:schemas-upnp-org:service:WANPPPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:");
+ }
+ else
+ throw new NotSupportedException("Received non-supported device type");
+
+ // We have an internet gateway device now
+ return new UpnpNatDevice(localAddress, dataString, urn);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs
new file mode 100644
index 000000000..1160d3ac2
--- /dev/null
+++ b/Mono.Nat/Upnp/UpnpNatDevice.cs
@@ -0,0 +1,651 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// 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.IO;
+using System.Net;
+using System.Xml;
+using System.Text;
+using System.Diagnostics;
+using MediaBrowser.Controller.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;
+
+ internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType)
+ {
+ 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 = deviceInfo.Location.ToString();
+ this.serviceType = serviceType;
+
+ // Make sure we have no excess whitespace
+ locationDetails = locationDetails.Trim();
+
+ // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+ // Are we going to get addresses with the "http://" attached?
+ if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+ {
+ NatUtility.Log("Found device at: {0}", locationDetails);
+ // This bit strings out the "http://" from the string
+ locationDetails = locationDetails.Substring(7);
+
+ this.hostEndPoint = hostEndPoint;
+
+ NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
+
+ // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
+ // and port information
+ this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
+ }
+ else
+ {
+ NatUtility.Log("Couldn't decode address. Please send following string to the developer: ");
+ }
+ }
+
+ internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType)
+ {
+ 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];
+ this.serviceType = serviceType;
+
+ // Make sure we have no excess whitespace
+ locationDetails = locationDetails.Trim();
+
+ // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+ // Are we going to get addresses with the "http://" attached?
+ if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+ {
+ NatUtility.Log("Found device at: {0}", locationDetails);
+ // This bit strings out the "http://" from the string
+ locationDetails = locationDetails.Substring(7);
+
+ // We then split off the end of the string to get something like: 192.168.0.3:241 in our string
+ string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/'));
+
+ // From this we parse out the IP address and Port
+ if (hostAddressAndPort.IndexOf(':') > 0)
+ {
+ this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))),
+ Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ // there is no port specified, use default port (80)
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// The EndPoint that the device is at
+ /// </summary>
+ internal EndPoint HostEndPoint
+ {
+ get { return this.hostEndPoint; }
+ }
+
+ /// <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 relative url that we can use to control the port forwarding
+ /// </summary>
+ internal string ControlUrl
+ {
+ get { return this.controlUrl; }
+ }
+
+ /// <summary>
+ /// The service type we're using on the device
+ /// </summary>
+ public string ServiceType
+ {
+ get { return serviceType; }
+ }
+
+ /// <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>
+ /// Maps the specified port to this computer
+ /// </summary>
+ public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ 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??
+ }
+
+ 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);
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ //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();
+ }
+ }
+
+ /// <summary>
+ /// Overridden.
+ /// </summary>
+ /// <returns></returns>
+ public override string ToString( )
+ {
+ //GetExternalIP is blocking and can throw exceptions, can't use it here.
+ 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