commit
00d974ce38
|
@ -1,10 +1,20 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Dlna
|
namespace MediaBrowser.Controller.Dlna
|
||||||
{
|
{
|
||||||
public interface IDeviceDiscovery
|
public interface IDeviceDiscovery
|
||||||
{
|
{
|
||||||
event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
|
event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
|
||||||
event EventHandler<SsdpMessageEventArgs> DeviceLeft;
|
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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna
|
||||||
{
|
{
|
||||||
public interface ISsdpHandler
|
public interface ISsdpHandler
|
||||||
{
|
{
|
||||||
event EventHandler<SsdpMessageEventArgs> MessageReceived;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,8 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
/// <value>The image URL.</value>
|
/// <value>The image URL.</value>
|
||||||
public string ImageUrl { get; set; }
|
public string ImageUrl { get; set; }
|
||||||
|
|
||||||
|
public string LogoImageUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance has image.
|
/// Gets or sets a value indicating whether this instance has image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -14,8 +14,10 @@ using MediaBrowser.Dlna.Ssdp;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using Rssdp;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Main
|
namespace MediaBrowser.Dlna.Main
|
||||||
{
|
{
|
||||||
|
@ -38,12 +40,11 @@ namespace MediaBrowser.Dlna.Main
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
|
||||||
private readonly SsdpHandler _ssdpHandler;
|
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
private readonly List<string> _registeredServerIds = new List<string>();
|
|
||||||
private bool _ssdpHandlerStarted;
|
private bool _ssdpHandlerStarted;
|
||||||
private bool _dlnaServerStarted;
|
private bool _dlnaServerStarted;
|
||||||
|
private SsdpDevicePublisher _Publisher;
|
||||||
|
|
||||||
public DlnaEntryPoint(IServerConfigurationManager config,
|
public DlnaEntryPoint(IServerConfigurationManager config,
|
||||||
ILogManager logManager,
|
ILogManager logManager,
|
||||||
|
@ -58,7 +59,7 @@ namespace MediaBrowser.Dlna.Main
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
|
IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
@ -74,7 +75,6 @@ namespace MediaBrowser.Dlna.Main
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_deviceDiscovery = deviceDiscovery;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_ssdpHandler = (SsdpHandler)ssdpHandler;
|
|
||||||
_logger = logManager.GetLogger("Dlna");
|
_logger = logManager.GetLogger("Dlna");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ namespace MediaBrowser.Dlna.Main
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ssdpHandler.Start();
|
StartPublishing();
|
||||||
_ssdpHandlerStarted = true;
|
_ssdpHandlerStarted = true;
|
||||||
|
|
||||||
StartDeviceDiscovery();
|
StartDeviceDiscovery();
|
||||||
|
@ -165,13 +165,16 @@ namespace MediaBrowser.Dlna.Main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartPublishing()
|
||||||
|
{
|
||||||
|
_Publisher = new SsdpDevicePublisher();
|
||||||
|
}
|
||||||
|
|
||||||
private void StartDeviceDiscovery()
|
private void StartDeviceDiscovery()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler);
|
((DeviceDiscovery)_deviceDiscovery).Start();
|
||||||
|
|
||||||
//DlnaChannel.Current.Start(() => _registeredServerIds.ToList());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -199,8 +202,6 @@ namespace MediaBrowser.Dlna.Main
|
||||||
{
|
{
|
||||||
((DeviceDiscovery)_deviceDiscovery).Dispose();
|
((DeviceDiscovery)_deviceDiscovery).Dispose();
|
||||||
|
|
||||||
_ssdpHandler.Dispose();
|
|
||||||
|
|
||||||
_ssdpHandlerStarted = false;
|
_ssdpHandlerStarted = false;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -225,6 +226,14 @@ namespace MediaBrowser.Dlna.Main
|
||||||
|
|
||||||
private async Task RegisterServerEndpoints()
|
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))
|
foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
//if (IPAddress.IsLoopback(address))
|
//if (IPAddress.IsLoopback(address))
|
||||||
|
@ -234,25 +243,41 @@ namespace MediaBrowser.Dlna.Main
|
||||||
//}
|
//}
|
||||||
|
|
||||||
var addressString = address.ToString();
|
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>
|
var services = new List<string>
|
||||||
{
|
{
|
||||||
"upnp:rootdevice",
|
|
||||||
"urn:schemas-upnp-org:device:MediaServer:1",
|
"urn:schemas-upnp-org:device:MediaServer:1",
|
||||||
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||||
"urn:schemas-upnp-org:service:ConnectionManager:1",
|
"urn:schemas-upnp-org:service:ConnectionManager:1",
|
||||||
"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
|
"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
|
||||||
"uuid:" + udn
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_ssdpHandler.RegisterNotification(udn, uri, address, services);
|
var udn = (addressString).GetMD5().ToString("N");
|
||||||
|
|
||||||
_registeredServerIds.Add(udn);
|
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);
|
||||||
|
|
||||||
|
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()
|
public void DisposeDlnaServer()
|
||||||
{
|
{
|
||||||
foreach (var id in _registeredServerIds)
|
if (_Publisher != null)
|
||||||
{
|
{
|
||||||
try
|
var devices = _Publisher.Devices.ToList();
|
||||||
|
foreach (var device in devices)
|
||||||
{
|
{
|
||||||
_ssdpHandler.UnregisterNotification(id);
|
try
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
_Publisher.RemoveDevice(device);
|
||||||
{
|
}
|
||||||
_logger.ErrorException("Error unregistering server", ex);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error sending bye bye", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_Publisher.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_registeredServerIds.Clear();
|
|
||||||
|
|
||||||
_dlnaServerStarted = false;
|
_dlnaServerStarted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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')" />
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>none</DebugType>
|
<DebugType>none</DebugType>
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
|
||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
|
@ -50,8 +51,15 @@
|
||||||
<Reference Include="Patterns.Logging">
|
<Reference Include="Patterns.Logging">
|
||||||
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
|
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
|
||||||
</Reference>
|
</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" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
|
|
@ -19,6 +19,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.PlayTo
|
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;
|
string nts;
|
||||||
e.Headers.TryGetValue("NTS", out nts);
|
info.Headers.TryGetValue("NTS", out nts);
|
||||||
|
|
||||||
string usn;
|
string usn;
|
||||||
if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
|
if (!info.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
|
||||||
|
|
||||||
string nt;
|
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 &&
|
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
|
||||||
!_disposed)
|
!_disposed)
|
||||||
|
@ -653,7 +656,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_device.PlaybackProgress -= _device_PlaybackProgress;
|
_device.PlaybackProgress -= _device_PlaybackProgress;
|
||||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||||
_device.MediaChanged -= _device_MediaChanged;
|
_device.MediaChanged -= _device_MediaChanged;
|
||||||
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
//_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||||
_device.OnDeviceUnavailable = null;
|
_device.OnDeviceUnavailable = null;
|
||||||
|
|
||||||
_device.Dispose();
|
_device.Dispose();
|
||||||
|
|
|
@ -12,7 +12,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.PlayTo
|
namespace MediaBrowser.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
|
@ -61,16 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
_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;
|
string usn;
|
||||||
if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
|
if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
|
||||||
|
|
||||||
string nt;
|
string nt;
|
||||||
if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
|
if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
|
||||||
|
|
||||||
string location;
|
string location = info.Location.ToString();
|
||||||
if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty;
|
|
||||||
|
|
||||||
// It has to report that it's a media renderer
|
// It has to report that it's a media renderer
|
||||||
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
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);
|
_logger.Debug("Attempting to create PlayToController from location {0}", location);
|
||||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
|
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -121,7 +124,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
|
|
||||||
if (controller == null)
|
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;
|
string accessToken = null;
|
||||||
|
|
||||||
sessionInfo.SessionController = controller = new PlayToController(sessionInfo,
|
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()
|
public void Dispose()
|
||||||
|
|
|
@ -11,6 +11,8 @@ using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using Rssdp;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Ssdp
|
namespace MediaBrowser.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
|
@ -20,132 +22,43 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private SsdpHandler _ssdpHandler;
|
|
||||||
private readonly CancellationTokenSource _tokenSource;
|
private readonly CancellationTokenSource _tokenSource;
|
||||||
private readonly IServerApplicationHost _appHost;
|
|
||||||
|
|
||||||
public event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
|
||||||
public event EventHandler<SsdpMessageEventArgs> DeviceLeft;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||||
private readonly INetworkManager _networkManager;
|
|
||||||
|
|
||||||
public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost, INetworkManager networkManager)
|
private SsdpDeviceLocator _DeviceLocator;
|
||||||
|
|
||||||
|
public DeviceDiscovery(ILogger logger, IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_appHost = appHost;
|
|
||||||
_networkManager = networkManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IPAddress> GetLocalIpAddresses()
|
// Call this method from somewhere in your code to start the search.
|
||||||
{
|
public void BeginSearch()
|
||||||
return _networkManager.GetLocalIpAddresses().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start(SsdpHandler ssdpHandler)
|
|
||||||
{
|
{
|
||||||
_ssdpHandler = ssdpHandler;
|
_DeviceLocator = new SsdpDeviceLocator();
|
||||||
_ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived;
|
|
||||||
|
|
||||||
foreach (var localIp in GetLocalIpAddresses())
|
// (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
|
||||||
try
|
// DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
|
||||||
{
|
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
|
||||||
CreateListener(localIp);
|
|
||||||
}
|
// Connect our event handler so we process devices as they are found
|
||||||
catch (Exception e)
|
_DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
|
||||||
{
|
_DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
|
||||||
_logger.ErrorException("Failed to Initilize Socket", e);
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
|
private void StartAsyncSearch()
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateNotifier(IPAddress localIp)
|
|
||||||
{
|
{
|
||||||
Task.Factory.StartNew(async (o) =>
|
Task.Factory.StartNew(async (o) =>
|
||||||
{
|
{
|
||||||
|
@ -153,7 +66,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
_ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900));
|
await _DeviceLocator.SearchAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
|
var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
|
||||||
|
|
||||||
|
@ -165,66 +78,60 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
|
||||||
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);
|
|
||||||
|
|
||||||
socket.Bind(localEndpoint);
|
var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
return socket;
|
var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
|
||||||
|
|
||||||
private void TryCreateDevice(SsdpMessageEventArgs args)
|
var args = new GenericEventArgs<UpnpDeviceInfo>
|
||||||
{
|
|
||||||
string nts;
|
|
||||||
args.Headers.TryGetValue("NTS", out nts);
|
|
||||||
|
|
||||||
if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase))
|
Argument = new UpnpDeviceInfo
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
Location = e.DiscoveredDevice.DescriptionLocation,
|
||||||
{
|
Headers = headers
|
||||||
EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string usn;
|
|
||||||
if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
|
|
||||||
|
|
||||||
string nt;
|
|
||||||
if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
|
|
||||||
|
|
||||||
// Need to be able to download device description
|
|
||||||
string location;
|
|
||||||
if (!args.Headers.TryGetValue("Location", out location) ||
|
|
||||||
string.IsNullOrEmpty(location))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
|
EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var args = new GenericEventArgs<UpnpDeviceInfo>
|
||||||
|
{
|
||||||
|
Argument = new UpnpDeviceInfo
|
||||||
|
{
|
||||||
|
Location = e.DiscoveredDevice.DescriptionLocation,
|
||||||
|
Headers = headers
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
BeginSearch();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_ssdpHandler != null)
|
|
||||||
{
|
|
||||||
_ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
{
|
{
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
|
@ -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
|
public IEnumerable<UpnpDevice> RegisteredDevices
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -188,8 +104,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
RestartSocketListener();
|
RestartSocketListener();
|
||||||
ReloadAliveNotifier();
|
ReloadAliveNotifier();
|
||||||
|
|
||||||
CreateUnicastClient();
|
|
||||||
|
|
||||||
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
||||||
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,
|
public async void SendDatagram(string msg,
|
||||||
EndPoint endpoint,
|
EndPoint endpoint,
|
||||||
EndPoint localAddress,
|
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()
|
private void RestartSocketListener()
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
if (_isDisposed)
|
||||||
|
@ -329,8 +148,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
_multicastSocket = CreateMulticastSocket();
|
_multicastSocket = CreateMulticastSocket();
|
||||||
|
|
||||||
_logger.Info("MultiCast socket created");
|
_logger.Info("MultiCast socket created");
|
||||||
|
|
||||||
Receive();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
|
_config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||||
|
@ -414,7 +163,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
||||||
DisposeUnicastClient();
|
|
||||||
DisposeSocket();
|
DisposeSocket();
|
||||||
StopAliveNotifier();
|
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 readonly object _notificationTimerSyncLock = new object();
|
||||||
private int _aliveNotifierIntervalMs;
|
private int _aliveNotifierIntervalMs;
|
||||||
private void ReloadAliveNotifier()
|
private void ReloadAliveNotifier()
|
||||||
|
|
|
@ -175,9 +175,6 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
|
||||||
<Link>Configuration\AccessSchedule.cs</Link>
|
<Link>Configuration\AccessSchedule.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
|
|
||||||
<Link>Configuration\AutoOnOff.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
|
||||||
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
|
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -147,9 +147,6 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
|
||||||
<Link>Configuration\AccessSchedule.cs</Link>
|
<Link>Configuration\AccessSchedule.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
|
|
||||||
<Link>Configuration\AutoOnOff.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
|
||||||
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
|
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -1193,4 +1190,4 @@
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Configuration
|
|
||||||
{
|
|
||||||
public enum AutoOnOff
|
|
||||||
{
|
|
||||||
Auto,
|
|
||||||
Enabled,
|
|
||||||
Disabled
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -183,8 +183,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
|
|
||||||
public int RemoteClientBitrateLimit { get; set; }
|
public int RemoteClientBitrateLimit { get; set; }
|
||||||
|
|
||||||
public AutoOnOff EnableLibraryMonitor { get; set; }
|
|
||||||
|
|
||||||
public int SharingExpirationDays { get; set; }
|
public int SharingExpirationDays { get; set; }
|
||||||
|
|
||||||
public string[] Migrations { get; set; }
|
public string[] Migrations { get; set; }
|
||||||
|
@ -244,7 +242,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
// 5 minutes
|
// 5 minutes
|
||||||
MinResumeDurationSeconds = 300;
|
MinResumeDurationSeconds = 300;
|
||||||
|
|
||||||
EnableLibraryMonitor = AutoOnOff.Auto;
|
|
||||||
LibraryMonitorDelay = 60;
|
LibraryMonitorDelay = 60;
|
||||||
|
|
||||||
EnableInternetProviders = true;
|
EnableInternetProviders = true;
|
||||||
|
|
|
@ -89,7 +89,6 @@
|
||||||
<Compile Include="Chapters\RemoteChapterResult.cs" />
|
<Compile Include="Chapters\RemoteChapterResult.cs" />
|
||||||
<Compile Include="Collections\CollectionCreationResult.cs" />
|
<Compile Include="Collections\CollectionCreationResult.cs" />
|
||||||
<Compile Include="Configuration\AccessSchedule.cs" />
|
<Compile Include="Configuration\AccessSchedule.cs" />
|
||||||
<Compile Include="Configuration\AutoOnOff.cs" />
|
|
||||||
<Compile Include="Configuration\ChannelOptions.cs" />
|
<Compile Include="Configuration\ChannelOptions.cs" />
|
||||||
<Compile Include="Configuration\ChapterOptions.cs" />
|
<Compile Include="Configuration\ChapterOptions.cs" />
|
||||||
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
|
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
|
||||||
|
|
|
@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using MediaBrowser.Common.Threading;
|
using MediaBrowser.Common.Threading;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.EntryPoints
|
namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
|
@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ISsdpHandler _ssdp;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
private PeriodicTimer _timer;
|
private PeriodicTimer _timer;
|
||||||
private bool _isStarted;
|
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");
|
_logger = logmanager.GetLogger("PortMapper");
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_config = config;
|
_config = config;
|
||||||
_ssdp = ssdp;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _lastConfigIdentifier;
|
private string _lastConfigIdentifier;
|
||||||
|
@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
//NatUtility.Logger = new LogWriter(_logger);
|
NatUtility.Logger = _logger;
|
||||||
|
|
||||||
if (_config.Configuration.EnableUPnP)
|
if (_config.Configuration.EnableUPnP)
|
||||||
{
|
{
|
||||||
|
@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
_timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
_timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
_ssdp.MessageReceived += _ssdp_MessageReceived;
|
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
||||||
|
|
||||||
_lastConfigIdentifier = GetConfigIdentifier();
|
_lastConfigIdentifier = GetConfigIdentifier();
|
||||||
|
|
||||||
_isStarted = true;
|
_isStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearCreatedRules(object state)
|
private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
_createdRules = new List<string>();
|
var info = e.Argument;
|
||||||
_usnsHandled = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
|
|
||||||
{
|
|
||||||
var endpoint = e.EndPoint as IPEndPoint;
|
|
||||||
|
|
||||||
if (endpoint == null || e.LocalEndPoint == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string usn;
|
string usn;
|
||||||
if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
|
if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
|
||||||
|
|
||||||
string nt;
|
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
|
// Filter device type
|
||||||
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
|
||||||
|
@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
|
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
|
||||||
|
|
||||||
if (!_usnsHandled.Contains(identifier))
|
if (info.Location != null && !_usnsHandled.Contains(identifier))
|
||||||
{
|
{
|
||||||
_usnsHandled.Add(identifier);
|
_usnsHandled.Add(identifier);
|
||||||
|
|
||||||
_logger.Debug("Calling Nat.Handle on " + 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)
|
void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
var ex = e.ExceptionObject as Exception;
|
var ex = e.ExceptionObject as Exception;
|
||||||
|
@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
_timer = null;
|
_timer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ssdp.MessageReceived -= _ssdp_MessageReceived;
|
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -164,32 +164,16 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
Start();
|
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)
|
private bool IsLibraryMonitorEnabaled(BaseItem item)
|
||||||
{
|
{
|
||||||
var options = LibraryManager.GetLibraryOptions(item);
|
var options = LibraryManager.GetLibraryOptions(item);
|
||||||
|
|
||||||
if (options != null && options.SchemaVersion >= 1)
|
if (options != null)
|
||||||
{
|
{
|
||||||
return options.EnableRealtimeMonitor;
|
return options.EnableRealtimeMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
return EnableLibraryMonitor;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
|
|
|
@ -166,7 +166,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
|
||||||
var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
|
var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
|
||||||
if (imageIndex > -1)
|
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;
|
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 readonly object _channelCacheLock = new object();
|
||||||
private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName)
|
private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName)
|
||||||
{
|
{
|
||||||
|
@ -384,13 +418,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
|
||||||
episodeTitle = details.episodeTitle150;
|
episodeTitle = details.episodeTitle150;
|
||||||
}
|
}
|
||||||
|
|
||||||
string imageUrl = null;
|
|
||||||
|
|
||||||
if (details.hasImageArtwork)
|
|
||||||
{
|
|
||||||
imageUrl = details.images;
|
|
||||||
}
|
|
||||||
|
|
||||||
var showType = details.showType ?? string.Empty;
|
var showType = details.showType ?? string.Empty;
|
||||||
|
|
||||||
var info = new ProgramInfo
|
var info = new ProgramInfo
|
||||||
|
@ -406,7 +433,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
|
||||||
Audio = audioType,
|
Audio = audioType,
|
||||||
IsRepeat = repeat,
|
IsRepeat = repeat,
|
||||||
IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1,
|
IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1,
|
||||||
ImageUrl = imageUrl,
|
ImageUrl = details.primaryImage,
|
||||||
IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
|
IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
|
||||||
IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1,
|
IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1,
|
||||||
IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", 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;
|
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;
|
string url = null;
|
||||||
if (images.data != null)
|
|
||||||
{
|
|
||||||
var smallImages = images.data.Where(i => i.size == "Sm").ToList();
|
|
||||||
if (smallImages.Any())
|
|
||||||
{
|
|
||||||
images.data = smallImages;
|
|
||||||
}
|
|
||||||
var logoIndex = images.data.FindIndex(i => i.category == "Logo");
|
|
||||||
if (logoIndex == -1)
|
|
||||||
{
|
|
||||||
logoIndex = 0;
|
|
||||||
}
|
|
||||||
var uri = images.data[logoIndex].uri;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(uri))
|
var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (logoIndex == -1)
|
||||||
|
{
|
||||||
|
if (!returnDefaultImage)
|
||||||
{
|
{
|
||||||
if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
|
return null;
|
||||||
{
|
|
||||||
url = uri;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
url = apiUrl + "/image/" + uri;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//_logger.Debug("URL for image is : " + url);
|
logoIndex = 0;
|
||||||
}
|
}
|
||||||
|
var uri = images[logoIndex].uri;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(uri))
|
||||||
|
{
|
||||||
|
if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
url = uri;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
url = apiUrl + "/image/" + uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//_logger.Debug("URL for image is : " + url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1204,7 +1228,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
|
||||||
public List<Crew> crew { get; set; }
|
public List<Crew> crew { get; set; }
|
||||||
public string showType { get; set; }
|
public string showType { get; set; }
|
||||||
public bool hasImageArtwork { 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 imageID { get; set; }
|
||||||
public string md5 { get; set; }
|
public string md5 { get; set; }
|
||||||
public List<string> contentAdvisory { get; set; }
|
public List<string> contentAdvisory { get; set; }
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
|
void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
string server = null;
|
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;
|
string location;
|
||||||
if (e.Headers.TryGetValue("Location", out location))
|
if (info.Headers.TryGetValue("Location", out location))
|
||||||
{
|
{
|
||||||
//_logger.Debug("HdHomerun found at {0}", location);
|
//_logger.Debug("HdHomerun found at {0}", location);
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
||||||
{
|
{
|
||||||
|
@ -50,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
||||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
_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 st = null;
|
||||||
string nt = null;
|
string nt = null;
|
||||||
e.Headers.TryGetValue("ST", out st);
|
info.Headers.TryGetValue("ST", out st);
|
||||||
e.Headers.TryGetValue("NT", out nt);
|
info.Headers.TryGetValue("NT", out nt);
|
||||||
|
|
||||||
if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
|
string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
string location;
|
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);
|
_logger.Debug("SAT IP found at {0}", location);
|
||||||
|
|
||||||
|
|
|
@ -105,9 +105,6 @@
|
||||||
<Reference Include="UniversalDetector">
|
<Reference Include="UniversalDetector">
|
||||||
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
|
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Mono.Nat">
|
|
||||||
<HintPath>..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs">
|
<Compile Include="..\SharedVersion.cs">
|
||||||
|
@ -390,6 +387,10 @@
|
||||||
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
|
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
|
||||||
<Name>MediaBrowser.Model</Name>
|
<Name>MediaBrowser.Model</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
|
||||||
|
<Project>{d7453b88-2266-4805-b39b-2b5a2a33e1ba}</Project>
|
||||||
|
<Name>Mono.Nat</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Localization\Ratings\us.txt" />
|
<EmbeddedResource Include="Localization\Ratings\us.txt" />
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
|
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
|
||||||
<package id="Interfaces.IO" version="1.0.0.5" 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="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="morelinq" version="1.4.0" targetFramework="net45" />
|
||||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||||
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
|
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||||
|
@ -10,8 +10,9 @@
|
||||||
<RootNamespace>MediaBrowser.Server.Mono</RootNamespace>
|
<RootNamespace>MediaBrowser.Server.Mono</RootNamespace>
|
||||||
<AssemblyName>MediaBrowser.Server.Mono</AssemblyName>
|
<AssemblyName>MediaBrowser.Server.Mono</AssemblyName>
|
||||||
<StartupObject>MediaBrowser.Server.Mono.MainClass</StartupObject>
|
<StartupObject>MediaBrowser.Server.Mono.MainClass</StartupObject>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<configSections>
|
<configSections>
|
||||||
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
|
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
|
||||||
</configSections>
|
</configSections>
|
||||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<targets async="true"></targets>
|
<targets async="true"></targets>
|
||||||
</nlog>
|
</nlog>
|
||||||
<appSettings>
|
<appSettings>
|
||||||
<add key="DebugProgramDataPath" value="ProgramData-Server" />
|
<add key="DebugProgramDataPath" value="ProgramData-Server"/>
|
||||||
<add key="ReleaseProgramDataPath" value="ProgramData-Server" />
|
<add key="ReleaseProgramDataPath" value="ProgramData-Server"/>
|
||||||
</appSettings>
|
</appSettings>
|
||||||
<runtime>
|
<runtime>
|
||||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
|
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
|
||||||
<bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
|
<bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
|
||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
</assemblyBinding>
|
</assemblyBinding>
|
||||||
</runtime>
|
</runtime>
|
||||||
</configuration>
|
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
|
||||||
|
|
|
@ -549,7 +549,7 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
|
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
|
||||||
RegisterSingleInstance(SubtitleManager);
|
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);
|
ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
|
||||||
RegisterSingleInstance(ChapterManager);
|
RegisterSingleInstance(ChapterManager);
|
||||||
|
@ -566,8 +566,6 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
await sharingRepo.Initialize().ConfigureAwait(false);
|
await sharingRepo.Initialize().ConfigureAwait(false);
|
||||||
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
|
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
|
||||||
|
|
||||||
RegisterSingleInstance<ISsdpHandler>(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this));
|
|
||||||
|
|
||||||
var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false);
|
var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false);
|
||||||
RegisterSingleInstance(activityLogRepo);
|
RegisterSingleInstance(activityLogRepo);
|
||||||
RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
|
RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
|
||||||
|
|
|
@ -9,9 +9,10 @@
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>MediaBrowser.Server.Startup.Common</RootNamespace>
|
<RootNamespace>MediaBrowser.Server.Startup.Common</RootNamespace>
|
||||||
<AssemblyName>MediaBrowser.Server.Startup.Common</AssemblyName>
|
<AssemblyName>MediaBrowser.Server.Startup.Common</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
|
|
@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(Performance) = preSolution
|
GlobalSection(Performance) = preSolution
|
||||||
HasPerformanceSessions = true
|
HasPerformanceSessions = true
|
||||||
|
@ -522,6 +524,36 @@ Global
|
||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU
|
{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|x64.ActiveCfg = Release|Any CPU
|
||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
97
Mono.Nat/AbstractNatDevice.cs
Normal file
97
Mono.Nat/AbstractNatDevice.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
71
Mono.Nat/AsyncResults/AsyncResult.cs
Normal file
71
Mono.Nat/AsyncResults/AsyncResult.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
Mono.Nat/Enums/MapState.cs
Normal file
36
Mono.Nat/Enums/MapState.cs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
36
Mono.Nat/Enums/ProtocolType.cs
Normal file
36
Mono.Nat/Enums/ProtocolType.cs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
45
Mono.Nat/EventArgs/DeviceEventArgs.cs
Normal file
45
Mono.Nat/EventArgs/DeviceEventArgs.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
Mono.Nat/Exceptions/MappingException.cs
Normal file
87
Mono.Nat/Exceptions/MappingException.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Mono.Nat/IMapper.cs
Normal file
50
Mono.Nat/IMapper.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
62
Mono.Nat/INatDevice.cs
Normal file
62
Mono.Nat/INatDevice.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
51
Mono.Nat/ISearcher.cs
Normal file
51
Mono.Nat/ISearcher.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
123
Mono.Nat/Mapping.cs
Normal file
123
Mono.Nat/Mapping.cs
Normal file
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
Mono.Nat/Mono.Nat.csproj
Normal file
104
Mono.Nat/Mono.Nat.csproj
Normal file
|
@ -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>
|
9
Mono.Nat/NatProtocol.cs
Normal file
9
Mono.Nat/NatProtocol.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
namespace Mono.Nat
|
||||||
|
{
|
||||||
|
public enum NatProtocol
|
||||||
|
{
|
||||||
|
Upnp = 0,
|
||||||
|
Pmp = 1
|
||||||
|
}
|
||||||
|
}
|
264
Mono.Nat/NatUtility.cs
Normal file
264
Mono.Nat/NatUtility.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
Normal file
52
Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
Mono.Nat/Pmp/Mappers/PmpMapper.cs
Normal file
83
Mono.Nat/Pmp/Mappers/PmpMapper.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
Mono.Nat/Pmp/Pmp.cs
Normal file
118
Mono.Nat/Pmp/Pmp.cs
Normal file
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
Mono.Nat/Pmp/PmpConstants.cs
Normal file
56
Mono.Nat/Pmp/PmpConstants.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
347
Mono.Nat/Pmp/PmpNatDevice.cs
Normal file
347
Mono.Nat/Pmp/PmpNatDevice.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
Mono.Nat/Pmp/Searchers/PmpSearcher.cs
Normal file
149
Mono.Nat/Pmp/Searchers/PmpSearcher.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
Mono.Nat/Properties/AssemblyInfo.cs
Normal file
31
Mono.Nat/Properties/AssemblyInfo.cs
Normal file
|
@ -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
|
||||||
|
//
|
56
Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
Normal file
56
Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
Normal file
75
Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
110
Mono.Nat/Upnp/Mappers/UpnpMapper.cs
Normal file
110
Mono.Nat/Upnp/Mappers/UpnpMapper.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
Normal file
60
Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
Mono.Nat/Upnp/Messages/ErrorMessage.cs
Normal file
63
Mono.Nat/Upnp/Messages/ErrorMessage.cs
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
Mono.Nat/Upnp/Messages/GetServicesMessage.cs
Normal file
62
Mono.Nat/Upnp/Messages/GetServicesMessage.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
Normal file
75
Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
Normal file
57
Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
Mono.Nat/Upnp/Messages/UpnpMessage.cs
Normal file
132
Mono.Nat/Upnp/Messages/UpnpMessage.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
287
Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
Normal file
287
Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
Mono.Nat/Upnp/Upnp.cs
Normal file
83
Mono.Nat/Upnp/Upnp.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
651
Mono.Nat/Upnp/UpnpNatDevice.cs
Normal file
651
Mono.Nat/Upnp/UpnpNatDevice.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user