Merge pull request #2159 from MediaBrowser/dev

Dev
This commit is contained in:
Luke 2016-09-11 12:07:07 -04:00 committed by GitHub
commit 00d974ce38
65 changed files with 4337 additions and 708 deletions

View File

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

View File

@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna
{ {
public interface ISsdpHandler public interface ISsdpHandler
{ {
event EventHandler<SsdpMessageEventArgs> MessageReceived;
} }
} }

View File

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

View File

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

View File

@ -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" />

View File

@ -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();

View File

@ -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()

View File

@ -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;

View File

@ -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()

View File

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

View File

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

View File

@ -1,10 +0,0 @@

namespace MediaBrowser.Model.Configuration
{
public enum AutoOnOff
{
Auto,
Enabled,
Disabled
}
}

View File

@ -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;

View File

@ -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" />

View File

@ -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
{ {

View File

@ -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()

View File

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

View File

@ -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);

View File

@ -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);

View File

@ -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" />

View File

@ -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" />

View File

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

View File

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

View File

@ -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));

View File

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

View File

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

View 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);
}
}

View 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);
}
}
}

View 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
}
}

View 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
}
}

View 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; }
}
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,9 @@

namespace Mono.Nat
{
public enum NatProtocol
{
Upnp = 0,
Pmp = 1
}
}

264
Mono.Nat/NatUtility.cs Normal file
View 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);
}
}
}
}

View 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; }
}
}
}

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

View 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;
}
}

View 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);
}
}
}
}

View 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; }
}
}
}

View 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
//

View 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; }
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View 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;
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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View 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);
}
}
}

View 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
View 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);
}
}
}

View 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);
}
}
}