trim nat project

pull/702/head
Luke Pulverenti 8 years ago
parent 5dca85fe15
commit bdcaf5dd02

@ -6,8 +6,6 @@ using SharpCompress.Common;
using SharpCompress.Reader;
using SharpCompress.Reader.Zip;
using System.IO;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Common.Implementations.Archiving
{
@ -16,7 +14,7 @@ namespace MediaBrowser.Common.Implementations.Archiving
/// </summary>
public class ZipClient : IZipClient
{
private IFileSystem _fileSystem;
private readonly IFileSystem _fileSystem;
public ZipClient(IFileSystem fileSystem)
{

@ -91,9 +91,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
NatUtility.DeviceLost += NatUtility_DeviceLost;
// it is hard to say what one should do when an unhandled exception is raised
// because there isn't anything one can do about it. Probably save a log or ignored it.
NatUtility.UnhandledException += NatUtility_UnhandledException;
NatUtility.StartDiscovery();
_timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
@ -184,21 +181,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
}
}
void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var ex = e.ExceptionObject as Exception;
if (ex == null)
{
//_logger.Error("Unidentified error reported by Mono.Nat");
}
else
{
// Seeing some blank exceptions coming through here
//_logger.ErrorException("Error reported by Mono.Nat: ", ex);
}
}
void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
{
try
@ -287,7 +269,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= NatUtility_DeviceFound;
NatUtility.DeviceLost -= NatUtility_DeviceLost;
NatUtility.UnhandledException -= NatUtility_UnhandledException;
}
// Statements in try-block will no fail because StopDiscovery is a one-line
// method that was no chances to fail.

@ -52,43 +52,5 @@ namespace Mono.Nat
}
public abstract Task CreatePortMap(Mapping mapping);
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);
}
}

@ -37,26 +37,8 @@ namespace Mono.Nat
public interface INatDevice
{
Task 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; }
}

@ -56,20 +56,11 @@
<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\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" />
@ -89,6 +80,9 @@
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Upnp\AsyncResults\" />
</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.

@ -34,6 +34,7 @@ using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Logging;
@ -46,8 +47,6 @@ namespace Mono.Nat
public static event EventHandler<DeviceEventArgs> DeviceFound;
public static event EventHandler<DeviceEventArgs> DeviceLost;
public static event EventHandler<UnhandledExceptionEventArgs> UnhandledException;
private static List<ISearcher> controllers;
private static bool verbose;
@ -87,9 +86,8 @@ namespace Mono.Nat
DeviceLost(sender, args);
};
});
Thread t = new Thread(SearchAndListen);
t.IsBackground = true;
t.Start();
Task.Factory.StartNew(SearchAndListen, TaskCreationOptions.LongRunning);
}
internal static void Log(string format, params object[] args)
@ -99,7 +97,7 @@ namespace Mono.Nat
logger.Debug(format, args);
}
private static void SearchAndListen()
private static async Task SearchAndListen()
{
while (true)
{
@ -115,18 +113,19 @@ namespace Mono.Nat
}
foreach (ISearcher s in controllers)
{
if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol))
{
Log("Searching for: {0}", s.GetType().Name);
s.Search();
s.Search();
}
}
}
catch (Exception e)
{
if (UnhandledException != null)
UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false));
}
Thread.Sleep(10);
await Task.Delay(100).ConfigureAwait(false);
}
}

@ -34,234 +34,166 @@ using System.Threading.Tasks;
namespace Mono.Nat.Pmp
{
internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice>
{
private AsyncResult externalIpResult;
private bool pendingOp;
private IPAddress localAddress;
private IPAddress publicAddress;
internal PmpNatDevice (IPAddress localAddress, IPAddress publicAddress)
{
this.localAddress = localAddress;
this.publicAddress = publicAddress;
}
public override IPAddress LocalAddress
{
get { return localAddress; }
}
internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice>
{
private IPAddress localAddress;
private IPAddress publicAddress;
public override Task CreatePortMap(Mapping mapping)
{
CreatePortMap(mapping, true);
return Task.FromResult(true);
}
private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState)
internal PmpNatDevice(IPAddress localAddress, IPAddress publicAddress)
{
if (pendingOp == true)
throw new InvalidOperationException("Can only have one simultaenous async operation");
pendingOp = true;
result = new AsyncResult(callback, asyncState);
this.localAddress = localAddress;
this.publicAddress = publicAddress;
}
private void EndOp(IAsyncResult supplied, ref AsyncResult actual)
public override IPAddress LocalAddress
{
if (supplied == null)
throw new ArgumentNullException("result");
get { return localAddress; }
}
if (supplied != actual)
throw new ArgumentException("Supplied IAsyncResult does not match the stored result");
public override Task CreatePortMap(Mapping mapping)
{
return InternalCreatePortMapAsync(mapping, true);
}
if (!supplied.IsCompleted)
supplied.AsyncWaitHandle.WaitOne();
public override bool Equals(object obj)
{
PmpNatDevice device = obj as PmpNatDevice;
return (device == null) ? false : this.Equals(device);
}
if (actual.StoredException != null)
throw actual.StoredException;
public override int GetHashCode()
{
return this.publicAddress.GetHashCode();
}
pendingOp = false;
actual = null;
public bool Equals(PmpNatDevice other)
{
return (other == null) ? false : this.publicAddress.Equals(other.publicAddress);
}
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 async Task<Mapping> InternalCreatePortMapAsync(Mapping mapping, bool create)
{
var package = new List<byte>();
package.Add(PmpConstants.Version);
package.Add(mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp);
package.Add(0); //reserved
package.Add(0); //reserved
package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)mapping.PrivatePort)));
package.AddRange(
BitConverter.GetBytes(create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0));
package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(mapping.Lifetime)));
try
{
byte[] buffer = package.ToArray();
int attempt = 0;
int delay = PmpConstants.RetryDelay;
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)));
using (var udpClient = new UdpClient())
{
var cancellationTokenSource = new CancellationTokenSource();
CreatePortMapAsyncState state = new CreatePortMapAsyncState ();
state.Buffer = package.ToArray ();
state.Mapping = mapping;
while (attempt < PmpConstants.RetryAttempts)
{
await udpClient.SendAsync(buffer, buffer.Length,
new IPEndPoint(LocalAddress, PmpConstants.ServerPort));
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);
if (attempt == 0)
{
Task.Run(() => CreatePortMapListen(udpClient, mapping, cancellationTokenSource.Token));
}
int attempt = 0;
int delay = PmpConstants.RetryDelay;
ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState);
attempt++;
delay *= 2;
await Task.Delay(delay).ConfigureAwait(false);
}
while (attempt < PmpConstants.RetryAttempts && !listenState.Success) {
udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort));
listenState.UdpClientReady.Set();
cancellationTokenSource.Cancel();
}
}
catch (OperationCanceledException)
{
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;
}
catch (Exception e)
{
string type = create ? "create" : "delete";
string message = String.Format("Failed to {0} portmap (protocol={1}, private port={2}) {3}",
type,
mapping.Protocol,
mapping.PrivatePort,
e.Message);
NatUtility.Log(message);
var pmpException = e as MappingException;
throw new MappingException(message, pmpException);
}
return mapping;
}
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)
private async void CreatePortMapListen(UdpClient udpClient, Mapping mapping, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
byte[] data;
try
{
data = udpClient.Receive(ref endPoint);
}
catch (SocketException)
{
state.Success = false;
return;
}
var result = await udpClient.ReceiveAsync().ConfigureAwait(false);
var endPoint = result.RemoteEndPoint;
byte[] data = data = result.Buffer;
catch (ObjectDisposedException)
{
state.Success = false;
return;
}
if (data.Length < 16)
continue;
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;
if (data[0] != PmpConstants.Version)
continue;
short resultCode = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 2));
uint epoch = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 4));
var opCode = (byte)(data[1] & 127);
int privatePort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 8));
int publicPort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 10));
var protocol = Protocol.Tcp;
if (opCode == PmpConstants.OperationCodeUdp)
protocol = Protocol.Udp;
uint lifetime = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 12));
short resultCode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 2));
int epoch = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 4));
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
short privatePort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 8));
short publicPort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10));
var lifetime = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 12));
if (privatePort < 0 || publicPort < 0 || resultCode != PmpConstants.ResultCodeSuccess)
{
//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);
var errors = new[]
{
"Success",
"Unsupported Version",
"Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off)"
,
"Network Failure (e.g. NAT box itself has not obtained a DHCP lease)",
"Out of resources (NAT box cannot create any more mappings at this time)",
"Unsupported opcode"
};
throw new MappingException(resultCode, errors[resultCode]);
}
state.Success = true;
}
}
}
if (lifetime == 0) return; //mapping was deleted
//mapping was created
//TODO: verify that the private port+protocol are a match
mapping.PublicPort = publicPort;
mapping.Protocol = protocol;
mapping.Expiration = DateTime.Now.AddSeconds(lifetime);
return;
}
}
/// <summary>
/// Overridden.
/// </summary>
/// <returns></returns>
public override string ToString( )
public override string ToString()
{
return String.Format( "PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}",
this.localAddress, this.publicAddress, this.LastSeen );
return String.Format("PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}",
this.localAddress, this.publicAddress, this.LastSeen);
}
private class CreatePortMapAsyncState
{
internal byte[] Buffer;
internal ManualResetEvent ResetEvent = new ManualResetEvent (false);
internal Mapping Mapping;
internal bool Success;
}
private class CreatePortMapListenState
{
internal volatile bool Success;
internal Mapping Mapping;
internal UdpClient UdpClient;
internal ManualResetEvent UdpClientReady;
internal CreatePortMapListenState (CreatePortMapAsyncState state, UdpClient client)
{
Mapping = state.Mapping;
UdpClient = client; UdpClientReady = new ManualResetEvent(false);
}
}
}
}
}

@ -1,56 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
namespace Mono.Nat.Upnp
{
internal class GetAllMappingsAsyncResult : PortMapAsyncResult
{
private List<Mapping> mappings;
private Mapping specificMapping;
public GetAllMappingsAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
: base(request, callback, asyncState)
{
mappings = new List<Mapping>();
}
public List<Mapping> Mappings
{
get { return this.mappings; }
}
public Mapping SpecificMapping
{
get { return this.specificMapping; }
set { this.specificMapping = value; }
}
}
}

@ -1,75 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Net;
using System.Threading;
namespace Mono.Nat.Upnp
{
internal class PortMapAsyncResult : AsyncResult
{
private WebRequest request;
private MessageBase savedMessage;
protected PortMapAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
: base (callback, asyncState)
{
this.request = request;
}
internal WebRequest Request
{
get { return this.request; }
set { this.request = value; }
}
internal MessageBase SavedMessage
{
get { return this.savedMessage; }
set { this.savedMessage = value; }
}
internal static PortMapAsyncResult Create (MessageBase message, WebRequest request, AsyncCallback storedCallback, object asyncState)
{
if (message is GetGenericPortMappingEntry)
return new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
if (message is GetSpecificPortMappingEntryMessage)
{
GetSpecificPortMappingEntryMessage mapMessage = (GetSpecificPortMappingEntryMessage)message;
GetAllMappingsAsyncResult result = new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
result.SpecificMapping = new Mapping(mapMessage.protocol, 0, mapMessage.externalPort, 0);
return result;
}
return new PortMapAsyncResult(request, storedCallback, asyncState);
}
}
}

@ -59,10 +59,5 @@ namespace Mono.Nat.Upnp
{
throw new NotImplementedException();
}
public override System.Net.WebRequest Encode(out byte[] body)
{
throw new NotImplementedException();
}
}
}

@ -60,17 +60,6 @@ namespace Mono.Nat.Upnp
}
}
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;
}
public override HttpRequestOptions Encode()
{
var req = new HttpRequestOptions();

@ -71,25 +71,5 @@ namespace Mono.Nat.Upnp
writer.Flush();
return CreateRequest("AddPortMapping", builder.ToString());
}
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);
}
}
}

@ -1,71 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Net;
using System.IO;
using System.Text;
using System.Xml;
using MediaBrowser.Common.Net;
namespace Mono.Nat.Upnp
{
internal class DeletePortMappingMessage : MessageBase
{
private Mapping mapping;
public DeletePortMappingMessage(Mapping mapping, UpnpNatDevice device)
: base(device)
{
this.mapping = mapping;
}
public override HttpRequestOptions Encode()
{
StringBuilder builder = new StringBuilder(256);
XmlWriter writer = CreateWriter(builder);
WriteFullElement(writer, "NewRemoteHost", string.Empty);
WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture));
WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
writer.Flush();
return CreateRequest("DeletePortMapping", builder.ToString());
}
public override WebRequest Encode(out byte[] body)
{
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);
}
}
}

@ -1,56 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using MediaBrowser.Common.Net;
namespace Mono.Nat.Upnp
{
internal class GetExternalIPAddressMessage : MessageBase
{
#region Constructors
public GetExternalIPAddressMessage(UpnpNatDevice device)
:base(device)
{
}
#endregion
public override HttpRequestOptions Encode()
{
return CreateRequest("GetExternalIPAddress", string.Empty);
}
public override WebRequest Encode(out byte[] body)
{
return CreateRequest("GetExternalIPAddress", string.Empty, out body);
}
}
}

@ -1,67 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using MediaBrowser.Common.Net;
namespace Mono.Nat.Upnp
{
internal class GetGenericPortMappingEntry : MessageBase
{
private int index;
public GetGenericPortMappingEntry(int index, UpnpNatDevice device)
:base(device)
{
this.index = index;
}
public override HttpRequestOptions Encode()
{
StringBuilder sb = new StringBuilder(128);
XmlWriter writer = CreateWriter(sb);
WriteFullElement(writer, "NewPortMappingIndex", index.ToString());
writer.Flush();
return CreateRequest("GetGenericPortMappingEntry", sb.ToString());
}
public override System.Net.WebRequest Encode(out byte[] body)
{
StringBuilder sb = new StringBuilder(128);
XmlWriter writer = CreateWriter(sb);
WriteFullElement(writer, "NewPortMappingIndex", index.ToString());
writer.Flush();
return CreateRequest("GetGenericPortMappingEntry", sb.ToString(), out body);
}
}
}

@ -1,74 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Net;
using MediaBrowser.Common.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);
}
public override HttpRequestOptions Encode()
{
StringBuilder sb = new StringBuilder(64);
XmlWriter writer = CreateWriter(sb);
WriteFullElement(writer, "NewRemoteHost", string.Empty);
WriteFullElement(writer, "NewExternalPort", externalPort.ToString());
WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP");
writer.Flush();
return CreateRequest("GetSpecificPortMappingEntry", sb.ToString());
}
}
}

@ -44,10 +44,5 @@ namespace Mono.Nat.Upnp
{
throw new NotImplementedException();
}
public override System.Net.WebRequest Encode(out byte[] body)
{
throw new NotImplementedException();
}
}
}

@ -1,51 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using MediaBrowser.Common.Net;
namespace Mono.Nat.Upnp
{
internal class DeletePortMapResponseMessage : MessageBase
{
public DeletePortMapResponseMessage()
:base(null)
{
}
public override HttpRequestOptions Encode()
{
throw new NotSupportedException();
}
public override System.Net.WebRequest Encode(out byte[] body)
{
throw new NotSupportedException();
}
}
}

@ -1,59 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using MediaBrowser.Common.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 HttpRequestOptions Encode()
{
throw new NotImplementedException();
}
public override WebRequest Encode(out byte[] body)
{
throw new NotImplementedException();
}
}
}

@ -1,114 +0,0 @@
//
// Authors:
// Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2006 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using MediaBrowser.Common.Net;
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 HttpRequestOptions Encode()
{
throw new NotImplementedException();
}
public override System.Net.WebRequest Encode(out byte[] body)
{
throw new NotImplementedException();
}
}
}

@ -45,33 +45,6 @@ namespace Mono.Nat.Upnp
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;
}
protected HttpRequestOptions CreateRequest(string upnpMethod, string methodParameters)
{
string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl;
@ -98,50 +71,7 @@ namespace Mono.Nat.Upnp
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 HttpRequestOptions Encode();
public abstract WebRequest Encode(out byte[] body);
public virtual string Method
{

@ -70,7 +70,7 @@ namespace Mono.Nat.Upnp
// FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
// Are we going to get addresses with the "http://" attached?
if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
if (locationDetails.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
{
NatUtility.Log("Found device at: {0}", locationDetails);
// This bit strings out the "http://" from the string
@ -98,7 +98,7 @@ namespace Mono.Nat.Upnp
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];
string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.OrdinalIgnoreCase) + 9).Split('\r')[0];
this.serviceType = serviceType;
// Make sure we have no excess whitespace
@ -106,7 +106,7 @@ namespace Mono.Nat.Upnp
// FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
// Are we going to get addresses with the "http://" attached?
if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
if (locationDetails.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
{
NatUtility.Log("Found device at: {0}", locationDetails);
// This bit strings out the "http://" from the string
@ -171,189 +171,12 @@ namespace Mono.Nat.Upnp
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);
}
public override Task CreatePortMap(Mapping mapping)
{
CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
return _httpClient.SendAsync(message.Encode(), message.Method);
}
/// <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;
@ -373,256 +196,6 @@ namespace Mono.Nat.Upnp
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 async Task<bool> GetServicesList()
{
// Create a HTTPWebRequest to download the list of services the device offers
var requestOptions = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger).Encode();
requestOptions.BufferContent = false;
using (var response = await _httpClient.Get(requestOptions).ConfigureAwait(false))
{
return ServicesReceived(response);
}
}
private bool ServicesReceived(Stream s)
{
int abortCount = 0;
int bytesRead = 0;
byte[] buffer = new byte[10240];
StringBuilder servicesXml = new StringBuilder();
XmlDocument xmldoc = new XmlDocument();
while (true)
{
bytesRead = s.Read(buffer, 0, buffer.Length);
servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
try
{
xmldoc.LoadXml(servicesXml.ToString());
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)
{
return false;
}
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);
return true;
}
}
}
//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
return false;
}
/// <summary>
/// Overridden.
/// </summary>

Loading…
Cancel
Save