aboutsummaryrefslogtreecommitdiff
path: root/Mono.Nat/Pmp/PmpNatDevice.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Mono.Nat/Pmp/PmpNatDevice.cs')
-rw-r--r--Mono.Nat/Pmp/PmpNatDevice.cs322
1 files changed, 127 insertions, 195 deletions
diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs
index 0deaf4af80..adb08fce3a 100644
--- a/Mono.Nat/Pmp/PmpNatDevice.cs
+++ b/Mono.Nat/Pmp/PmpNatDevice.cs
@@ -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);
- }
- }
- }
+ }
} \ No newline at end of file