commit
00d974ce38
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue