aboutsummaryrefslogtreecommitdiff
path: root/Mono.Nat
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2016-09-11 03:33:53 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2016-09-11 03:33:53 -0400
commit62d9eb1ec7da1b7017818e5620c2334ad336ac2f (patch)
tree6a9a158fbf7be50c4789d7991fe0d4c3d924d5ab /Mono.Nat
parentb4217f2800f89658d9ffc2651327d4d6582e41bd (diff)
rework upnp discovery
Diffstat (limited to 'Mono.Nat')
-rw-r--r--Mono.Nat/AbstractNatDevice.cs97
-rw-r--r--Mono.Nat/AsyncResults/AsyncResult.cs71
-rw-r--r--Mono.Nat/Enums/MapState.cs36
-rw-r--r--Mono.Nat/Enums/ProtocolType.cs36
-rw-r--r--Mono.Nat/EventArgs/DeviceEventArgs.cs45
-rw-r--r--Mono.Nat/Exceptions/MappingException.cs87
-rw-r--r--Mono.Nat/IMapper.cs50
-rw-r--r--Mono.Nat/INatDevice.cs62
-rw-r--r--Mono.Nat/ISearcher.cs51
-rw-r--r--Mono.Nat/Mapping.cs123
-rw-r--r--Mono.Nat/Mono.Nat.csproj104
-rw-r--r--Mono.Nat/NatProtocol.cs9
-rw-r--r--Mono.Nat/NatUtility.cs264
-rw-r--r--Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs52
-rw-r--r--Mono.Nat/Pmp/Mappers/PmpMapper.cs83
-rw-r--r--Mono.Nat/Pmp/Pmp.cs118
-rw-r--r--Mono.Nat/Pmp/PmpConstants.cs56
-rw-r--r--Mono.Nat/Pmp/PmpNatDevice.cs347
-rw-r--r--Mono.Nat/Pmp/Searchers/PmpSearcher.cs149
-rw-r--r--Mono.Nat/Properties/AssemblyInfo.cs31
-rw-r--r--Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs56
-rw-r--r--Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs75
-rw-r--r--Mono.Nat/Upnp/Mappers/UpnpMapper.cs110
-rw-r--r--Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/ErrorMessage.cs63
-rw-r--r--Mono.Nat/Upnp/Messages/GetServicesMessage.cs62
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs75
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs57
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs51
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs55
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs46
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs44
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs53
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs108
-rw-r--r--Mono.Nat/Upnp/Messages/UpnpMessage.cs132
-rw-r--r--Mono.Nat/Upnp/Searchers/UpnpSearcher.cs287
-rw-r--r--Mono.Nat/Upnp/Upnp.cs83
-rw-r--r--Mono.Nat/Upnp/UpnpNatDevice.cs651
39 files changed, 3999 insertions, 0 deletions
diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs
new file mode 100644
index 000000000..046cfc10f
--- /dev/null
+++ b/Mono.Nat/AbstractNatDevice.cs
@@ -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);
+ }
+}
diff --git a/Mono.Nat/AsyncResults/AsyncResult.cs b/Mono.Nat/AsyncResults/AsyncResult.cs
new file mode 100644
index 000000000..e98e7d7ca
--- /dev/null
+++ b/Mono.Nat/AsyncResults/AsyncResult.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Enums/MapState.cs b/Mono.Nat/Enums/MapState.cs
new file mode 100644
index 000000000..5ed2abd8f
--- /dev/null
+++ b/Mono.Nat/Enums/MapState.cs
@@ -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
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs
new file mode 100644
index 000000000..a1f5cbb0e
--- /dev/null
+++ b/Mono.Nat/Enums/ProtocolType.cs
@@ -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
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs
new file mode 100644
index 000000000..fbbbf63e3
--- /dev/null
+++ b/Mono.Nat/EventArgs/DeviceEventArgs.cs
@@ -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; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Exceptions/MappingException.cs b/Mono.Nat/Exceptions/MappingException.cs
new file mode 100644
index 000000000..bb2e6a69d
--- /dev/null
+++ b/Mono.Nat/Exceptions/MappingException.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/IMapper.cs b/Mono.Nat/IMapper.cs
new file mode 100644
index 000000000..b18e6cff2
--- /dev/null
+++ b/Mono.Nat/IMapper.cs
@@ -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);
+ }
+}
diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs
new file mode 100644
index 000000000..c9f27055b
--- /dev/null
+++ b/Mono.Nat/INatDevice.cs
@@ -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; }
+ }
+}
diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs
new file mode 100644
index 000000000..56e438105
--- /dev/null
+++ b/Mono.Nat/ISearcher.cs
@@ -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; }
+ }
+}
diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs
new file mode 100644
index 000000000..dd49404c6
--- /dev/null
+++ b/Mono.Nat/Mapping.cs
@@ -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 );
+ }
+ }
+}
diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj
new file mode 100644
index 000000000..9c2781433
--- /dev/null
+++ b/Mono.Nat/Mono.Nat.csproj
@@ -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> \ No newline at end of file
diff --git a/Mono.Nat/NatProtocol.cs b/Mono.Nat/NatProtocol.cs
new file mode 100644
index 000000000..ade8d921c
--- /dev/null
+++ b/Mono.Nat/NatProtocol.cs
@@ -0,0 +1,9 @@
+
+namespace Mono.Nat
+{
+ public enum NatProtocol
+ {
+ Upnp = 0,
+ Pmp = 1
+ }
+}
diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs
new file mode 100644
index 000000000..6d91d2513
--- /dev/null
+++ b/Mono.Nat/NatUtility.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
new file mode 100644
index 000000000..c8ccf5435
--- /dev/null
+++ b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
@@ -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; }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/Mappers/PmpMapper.cs b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
new file mode 100644
index 000000000..f33ca44c3
--- /dev/null
+++ b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/Pmp.cs b/Mono.Nat/Pmp/Pmp.cs
new file mode 100644
index 000000000..6795561b1
--- /dev/null
+++ b/Mono.Nat/Pmp/Pmp.cs
@@ -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.
+ }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs
new file mode 100644
index 000000000..ff3eb6230
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpConstants.cs
@@ -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;
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs
new file mode 100644
index 000000000..9a2962c4d
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpNatDevice.cs
@@ -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);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs
new file mode 100644
index 000000000..df0273ccb
--- /dev/null
+++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs
@@ -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; }
+ }
+ }
+}
diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..c3c3101de
--- /dev/null
+++ b/Mono.Nat/Properties/AssemblyInfo.cs
@@ -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
+// \ No newline at end of file
diff --git a/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
new file mode 100644
index 000000000..51ecfbaf0
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
@@ -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; }
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
new file mode 100644
index 000000000..d8ac3fe61
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs
new file mode 100644
index 000000000..6f2716805
--- /dev/null
+++ b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
new file mode 100644
index 000000000..87f5835a6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
new file mode 100644
index 000000000..ce5270e9b
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
@@ -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();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
new file mode 100644
index 000000000..c5d7bce70
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
@@ -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;
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
new file mode 100644
index 000000000..da650fb41
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
new file mode 100644
index 000000000..d9be89a69
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
new file mode 100644
index 000000000..8f97002ea
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
new file mode 100644
index 000000000..c0c555881
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
new file mode 100644
index 000000000..314468ece
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
new file mode 100644
index 000000000..e75926b09
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
@@ -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();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
new file mode 100644
index 000000000..1fce4eb04
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
@@ -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();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
new file mode 100644
index 000000000..ee4b18cd1
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
@@ -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();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
new file mode 100644
index 000000000..b11bfa027
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
@@ -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();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
new file mode 100644
index 000000000..44c16eec6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
new file mode 100644
index 000000000..edc5a5d76
--- /dev/null
+++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
@@ -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; }
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs
new file mode 100644
index 000000000..e44a51c24
--- /dev/null
+++ b/Mono.Nat/Upnp/Upnp.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs
new file mode 100644
index 000000000..1160d3ac2
--- /dev/null
+++ b/Mono.Nat/Upnp/UpnpNatDevice.cs
@@ -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);
+ }
+ }
+} \ No newline at end of file