diff options
Diffstat (limited to 'Mono.Nat/Pmp/PmpNatDevice.cs')
| -rw-r--r-- | Mono.Nat/Pmp/PmpNatDevice.cs | 322 |
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 |
