using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using Microsoft.Win32;
using System.Configuration;
using TCMPortMapper.Helpers;
using Yaulw.Registry;
using Yaulw.File;
namespace TCMPortMapper
{
///
/// Status of PortMapping object.
///
public enum PortMappingStatus
{
Unmapped = 0,
Trying = 1,
Mapped = 2
}
///
/// Protocol used for PortMapping object.
///
public enum PortMappingTransportProtocol
{
UDP = 1,
TCP = 2,
Both = 3
}
public class PortMapping
{
private UInt16 localPort;
private UInt16 externalPort;
private UInt16 desiredExternalPort;
private PortMappingStatus mappingStatus;
private PortMappingTransportProtocol transportProtocol;
private string description;
public PortMapping(UInt16 localPort, UInt16 desiredExternalPort, PortMappingTransportProtocol protocol, string description)
{
this.localPort = localPort;
this.desiredExternalPort = desiredExternalPort;
this.transportProtocol = protocol;
this.description = description;
this.mappingStatus = PortMappingStatus.Unmapped;
}
public UInt16 LocalPort
{
get { return localPort; }
}
public UInt16 DesiredExternalPort
{
get { return desiredExternalPort; }
}
public PortMappingTransportProtocol TransportProtocol
{
get { return transportProtocol; }
}
public UInt16 ExternalPort
{
get { return externalPort; }
}
public PortMappingStatus MappingStatus
{
get { return mappingStatus; }
}
public string Description
{
get { return description; }
}
internal void SetExternalPort(UInt16 port)
{
externalPort = port;
}
internal void SetMappingStatus(PortMappingStatus newMappingStatus)
{
if (mappingStatus != newMappingStatus)
{
mappingStatus = newMappingStatus;
if (mappingStatus == PortMappingStatus.Unmapped)
{
externalPort = 0;
}
PortMapper.SharedInstance.OnDidChangeMappingStatus(this);
}
}
}
public class PortMapper
{
///
/// Singleton instance of class
///
private static PortMapper sharedInstance;
///
/// Static constructor.
/// - executes before any instance of the class is created.
/// - executes before any of the static members for the class are referenced.
/// - executes after the static field initializers (if any) for the class.
/// - executes at most one time during a single program instantiation.
/// - called automatically to initialize the class before the first instance is created or any static members are referenced.
///
static PortMapper()
{
sharedInstance = new PortMapper();
}
///
/// Returns the sharedInstance of the PortMapper.
/// This is the sole instance that is to be used throughout the library.
///
public static PortMapper SharedInstance
{
get { return sharedInstance; }
}
public delegate void PMExternalIPAddressDidChange(PortMapper sender, IPAddress ip);
public delegate void PMWillStartSearchForRouter(PortMapper sender);
public delegate void PMDidFinishSearchForRouter(PortMapper sender);
public delegate void PMDidStartWork(PortMapper sender);
public delegate void PMDidFinishWork(PortMapper sender);
public delegate void PMDidReceiveUPNPMappingTable(PortMapper sender, List mappings);
public delegate void PMDidChangeMappingStatus(PortMapper sender, PortMapping pm);
public delegate void PMCanAddPortMapping(PortMapper sender);
public event PMExternalIPAddressDidChange ExternalIPAddressDidChange;
public event PMWillStartSearchForRouter WillStartSearchForRouter;
public event PMDidFinishSearchForRouter DidFinishSearchForRouter;
public event PMDidStartWork DidStartWork;
public event PMDidFinishWork DidFinishWork;
public event PMDidReceiveUPNPMappingTable DidReceiveUPNPMappingTable;
public event PMDidChangeMappingStatus DidChangeMappingStatus;
public event PMCanAddPortMapping CanAddPortMapping;
private enum MappingProtocol
{
None = 0,
NATPMP = 1,
UPnP = 2
}
private enum MappingStatus
{
Failed = 0,
Trying = 1,
Works = 2
}
private NATPMPPortMapper natpmpPortMapper;
private UPnPPortMapper upnpPortMapper;
private List portMappings; // Active mappings, and mappings to add
private List portMappingsToRemove; // Active mappings that should be removed
private List existingUPnPPortMappingsToRemove;
private bool isRunning;
private MappingStatus natpmpStatus;
private MappingStatus upnpStatus;
private MappingProtocol mappingProtocol;
private String routerManufacturer;
private IPAddress routerIPAddress;
private IPAddress localIPAddress;
private IPAddress externalIPAddress;
private IPAddress externalIPAddressFromService;
private bool externalIPFromRouter;
private bool localIPOnRouterSubnet;
private int workCount;
private Object multiThreadLock = new Object();
private Object singleThreadLock = new Object();
private Object workLock = new Object();
private bool isGoingToSleep;
private bool isNetworkAvailable;
private bool requestedUPnPMappingTable;
private string _externalPortToMap;
private string _mappingDescription;
private Logging Logger;
private bool _isSafeForPortMapping;
private UInt16 _currentExternalPort;
private PortMapper()
{
natpmpPortMapper = new NATPMPPortMapper();
upnpPortMapper = new UPnPPortMapper();
portMappings = new List();
portMappingsToRemove = new List();
existingUPnPPortMappingsToRemove = new List();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Delegate Methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected virtual void OnExternalIPAddressDidChange()
{
if (ExternalIPAddressDidChange != null)
{
ExternalIPAddressDidChange(this, externalIPAddress);
//Invoke(ExternalIPAddressDidChange, this, externalIPAddress);
}
}
protected virtual void OnWillStartSearchForRouter()
{
if (WillStartSearchForRouter != null)
{
WillStartSearchForRouter(this);
//Invoke(WillStartSearchForRouter, this);
}
}
protected virtual void OnDidFinishSearchForRouter()
{
if (DidFinishSearchForRouter != null)
{
DidFinishSearchForRouter(this);
//Invoke(DidFinishSearchForRouter, this);
}
}
protected virtual void OnDidStartWork()
{
if (DidStartWork != null)
{
DidStartWork(this);
//Invoke(DidStartWork, this);
}
}
protected virtual void OnDidFinishWork()
{
if (DidFinishWork != null)
{
DidFinishWork(this);
//Invoke(DidFinishWork, this);
}
}
protected virtual void OnDidReceiveUPNPMappingTable(List mappings)
{
if (DidReceiveUPNPMappingTable != null)
{
Invoke(DidReceiveUPNPMappingTable, this, mappings);
}
}
internal virtual void OnDidChangeMappingStatus(PortMapping pm)
{
// It is vitally important that we Invoke this on a background thread!
// The mapping status is generally changed within the context of a lock on the mappings list.
// This lock is also used within public API methods,
// so if we don't use a background thread, there's a potential for deadlock.
Thread bgThread = new Thread(new ParameterizedThreadStart(OnDidChangeMappingStatusThread));
bgThread.IsBackground = true;
bgThread.Start(pm);
}
protected virtual void OnDidChangeMappingStatusThread(Object pm)
{
if (DidChangeMappingStatus != null)
{
DidChangeMappingStatus(this, (PortMapping)pm);
//Invoke(DidChangeMappingStatus, this, (PortMapping)pm);
}
}
protected virtual void OnCanAddPortMapping()
{
if (CanAddPortMapping != null)
{
CanAddPortMapping(this);
}
}
private System.ComponentModel.ISynchronizeInvoke mSynchronizingObject = null;
///
/// Set the ISynchronizeInvoke
/// object to use as the invoke object. When returning results from asynchronous calls,
/// the Invoke method on this object will be called to pass the results back
/// in a thread safe manner.
///
///
/// If using in conjunction with a form, it is highly recommended
/// that you pass your main form (window) in.
///
public System.ComponentModel.ISynchronizeInvoke SynchronizingObject
{
get { return mSynchronizingObject; }
set { mSynchronizingObject = value; }
}
private bool mAllowApplicationForms = true;
///
/// Allows the application to attempt to post async replies over the
/// application "main loop" by using the message queue of the first available
/// open form (window). This is retrieved through
/// Application.OpenForms.
///
/// Note: This is true by default.
///
public bool AllowApplicationForms
{
get { return mAllowApplicationForms; }
set { mAllowApplicationForms = value; }
}
private bool mAllowMultithreadedCallbacks = false;
///
/// If set to true, AllowApplicationForms
/// is set to false and SynchronizingObject is set
/// to null. Any time an asynchronous method needs to invoke a delegate method
/// it will run the method in its own thread.
///
///
/// If set to true, you will have to handle any synchronization needed.
/// If your application uses Windows.Forms or any other non-thread safe
/// library, then you will have to do your own invoking.
///
public bool AllowMultithreadedCallbacks
{
get { return mAllowMultithreadedCallbacks; }
set
{
mAllowMultithreadedCallbacks = value;
if (mAllowMultithreadedCallbacks)
{
mAllowApplicationForms = false;
mSynchronizingObject = null;
}
}
}
///
/// Helper method to obtain a proper invokeable object.
/// If an invokeable object is set, it's immediately returned.
/// Otherwise, an open windows form is returned if available.
///
/// An invokeable object, or null if none available.
private System.ComponentModel.ISynchronizeInvoke GetInvokeObject()
{
if (mSynchronizingObject != null) return mSynchronizingObject;
if (mAllowApplicationForms)
{
// Need to post it over control thread
System.Windows.Forms.FormCollection forms = System.Windows.Forms.Application.OpenForms;
if (forms != null && forms.Count > 0)
{
System.Windows.Forms.Control control = forms[0];
return control;
}
}
return null;
}
///
/// Calls a method using the objects invokable object (if provided).
/// Otherwise, it simply invokes the method normally.
///
///
/// The method to call.
///
///
/// The arguments to call the method with.
///
///
/// The result returned from method, or null if the method could not be invoked.
///
internal object Invoke(Delegate method, params object[] args)
{
System.ComponentModel.ISynchronizeInvoke invokeable = GetInvokeObject();
try
{
if (invokeable != null)
{
return invokeable.Invoke(method, args);
}
if (mAllowMultithreadedCallbacks)
{
return method.DynamicInvoke(args);
}
}
catch { }
return null;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Public Properties
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool IsRunning
{
get { return isRunning; }
}
public IPAddress LocalIPAddress
{
get { return localIPAddress; }
}
public IPAddress ExternalIPAddress
{
get { return externalIPAddress; }
}
public bool ExternalIPFromRouter
{
get { return externalIPFromRouter; }
}
public IPAddress ExternalIPAddressFromService
{
get { return externalIPAddressFromService; }
}
public String RouterManufacturer
{
get { return routerManufacturer; }
}
public IPAddress RouterIPAddress
{
get { return routerIPAddress; }
}
public String MappingProtocolName
{
get
{
if (mappingProtocol == MappingProtocol.NATPMP)
return "NAT-PMP";
else if (mappingProtocol == MappingProtocol.UPnP)
return "UPnP";
else
return "None";
}
}
public String DefaultLocalBonjourHostName
{
get { return System.Net.Dns.GetHostName() + ".local"; }
}
public string MappingDescription
{
get
{
return _mappingDescription;
}
}
///
/// We are safe to attempt port mapping if the server we are currently on
/// is on the router we are connected to
///
public bool IsSafeForPortMapping
{
get
{
return _isSafeForPortMapping;
}
}
public UInt16 CurrentExternalPort
{
get { return _currentExternalPort; }
set
{
if (_currentExternalPort != value)
{
_currentExternalPort = value;
}
}
}
public string ExternalPortToMap
{
get
{
return _externalPortToMap;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Internal Properties
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal List PortMappings
{
get { return portMappings; }
}
internal List PortMappingsToRemove
{
get { return portMappingsToRemove; }
}
internal List ExistingUPnPPortMappingsToRemove
{
get { return existingUPnPPortMappingsToRemove; }
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Public Methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Start(IPAddress externalIPFromService, string externalPortToMap, string portMapDescription, Logging Logger)
{
if (!isRunning)
{
// Initialize winsock
Win32.WSAData d;
Win32.WSAStartup(2, out d);
AddSystemEventDelegates();
AddNATPMPDelegates();
AddUPnPDelegates();
isRunning = true;
}
//Set up logger, external ip, & external port to map
externalIPAddressFromService = externalIPFromService;
_externalPortToMap = externalPortToMap;
_mappingDescription = portMapDescription;
this.Logger = Logger;
// Start the Thread
Refresh();
}
public void AddPortMapping()
{
Logger.Info("AddPortMapping() - Add port mapping is called with Local IP: {0} and Port: {1}", ExternalPortToMap, LocalIPAddress.ToString());
ReleaseLog.WriteInformation("Getting ready to add port mapping.");
try
{
//Add port mapping
if (ExternalIPFromRouter && IsSafeForPortMapping)
{
PortMapping pMapping = new PortMapping(Convert.ToUInt16(ExternalPortToMap), Convert.ToUInt16(ExternalPortToMap), PortMappingTransportProtocol.TCP, MappingDescription);
AddPortMapping(pMapping);
Logger.Info("AddPortMapping() - Added port mapping with Local IP: {0} and Port: {1}", ExternalPortToMap, LocalIPAddress.ToString());
ReleaseLog.WriteInformation("Added port mapping with Local IP: {0} and Port: {1}", ExternalPortToMap, LocalIPAddress.ToString());
}
}
catch (Exception ex)
{
Logger.Error("AddPortMapping() - Adding port mapping failed.");
ReleaseLog.WriteError("Adding port mapping failed.");
}
}
///
/// Asynchronously adds the given port mapping.
///
///
/// The port mapping to add.
/// Note: Many UPnP routers only support port mappings where localPort == externalPort.
///
private void AddPortMapping(PortMapping pm)
{
if (pm == null) return;
portMappings.Add(pm);
if (isRunning) UpdatePortMappings();
}
///
/// Asynchronously removes the given port mapping.
///
///
/// The port mapping to remove.
///
public void RemovePortMapping(PortMapping pm)
{
if (pm == null) return;
portMappings.Remove(pm);
if (pm.MappingStatus != PortMappingStatus.Unmapped)
{
portMappingsToRemove.Add(pm);
}
if (isRunning) UpdatePortMappings();
}
///
/// Asynchronously removes the given port mapping.
///
/// This method will also automatically refresh the UPnP mapping table,
/// and call the DidReceiveUPNPMappingTable delegate.
///
///
/// The port mapping to remove.
///
public void RemovePortMapping(ExistingUPnPPortMapping pm)
{
if (pm == null) return;
if (upnpStatus == MappingStatus.Works)
{
existingUPnPPortMappingsToRemove.Add(pm);
if (isRunning)
{
requestedUPnPMappingTable = true;
UpdatePortMappings();
}
}
}
///
/// Refreshes all port mapping information, and all port mappings.
///
public void Refresh()
{
// All public API methods are wrapped in a single thread lock.
// This frees users to invoke the public API from multiple threads, but provides us a bit of sanity.
//lock (singleThreadLock)
//{
if (isRunning)
{
//Thread bgThread = new Thread(new ThreadStart(RefreshThread));
//bgThread.Start();
RefreshThread();
}
//}
}
public void RequestUPnPMappingTable()
{
// All public API methods are wrapped in a single thread lock.
// This frees users to invoke the public API from multiple threads, but provides us a bit of sanity.
//lock (singleThreadLock)
//{
if (isRunning)
{
if (upnpStatus == MappingStatus.Works)
{
requestedUPnPMappingTable = true;
upnpPortMapper.UpdateExistingUPnPPortMappings();
}
}
//}
}
///
/// Asynchronously stops the port mapper.
/// All added port mappings will be removed.
///
public void Stop()
{
ReleaseLog.WriteInformation("Port Mapper: Attempting to Stop Port Mapper.");
if (isRunning)
{
RemoveSystemEventDelegates();
RemoveNATPMPDelegates();
RemoveUPnPDelegates();
isRunning = false;
if (natpmpStatus == MappingStatus.Works)
{
natpmpPortMapper.Stop();
}
if (upnpStatus == MappingStatus.Works)
{
upnpPortMapper.Stop();
}
Win32.WSACleanup();
ReleaseLog.WriteInformation("Port Mapper: Stopped Port Mapper.");
}
ReleaseLog.WriteInformation("Port Mapper: Attempt to stop port mapper skipped since it's currently not running.");
}
///
/// Synchronously stops the port mapper.
/// All added port mappings will be removed.
///
public void StopBlocking()
{
if (isRunning)
{
RemoveSystemEventDelegates();
RemoveNATPMPDelegates();
RemoveUPnPDelegates();
isRunning = false;
if (natpmpStatus == MappingStatus.Works)
{
natpmpPortMapper.StopBlocking();
}
if (upnpStatus == MappingStatus.Works)
{
upnpPortMapper.StopBlocking();
}
Win32.WSACleanup();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Refresh Thread
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void RefreshThread()
{
mappingProtocol = MappingProtocol.None;
externalIPAddress = null;
for (int i = 0; i < portMappings.Count; i++)
{
PortMapping pm = (PortMapping)portMappings[i];
//if (pm.MappingStatus == PortMappingStatus.Mapped)
//{
// pm.SetMappingStatus(PortMappingStatus.Unmapped);
//}
}
OnWillStartSearchForRouter();
DebugLog.WriteLine("RefreshThread");
// Update all the following variables:
// - routerIPAddress
// - routerManufacturer
// - localIPAddress
// - localIPAddressOnSubnet
//
// Note: The order in which we call the following methods matters.
// We must update the routerIPAddress before the others.
UpdateRouterIPAddress();
UpdateRouterManufacturer();
UpdateLocalIPAddress();
DebugLog.WriteLine("routerIPAddress : {0}", routerIPAddress);
DebugLog.WriteLine("routerManufacturer : {0}", routerManufacturer);
DebugLog.WriteLine("localIPAddress : {0}", localIPAddress);
DebugLog.WriteLine("localIPAddressOnSubnet: {0}", localIPOnRouterSubnet);
if (routerIPAddress != null)
{
if ((localIPAddress != null) && localIPOnRouterSubnet)
{
Logger.Info("Port Mapper: Local IP found via router.");
externalIPAddress = null;
if (IsIPv4AddressInPrivateSubnet(routerIPAddress))
{
natpmpStatus = MappingStatus.Trying;
upnpStatus = MappingStatus.Trying;
natpmpPortMapper.Refresh();
upnpPortMapper.Refresh();
}
else
{
Logger.Info("Port Mapper: External IP found via router.");
natpmpStatus = MappingStatus.Failed;
upnpStatus = MappingStatus.Failed;
externalIPAddress = localIPAddress;
mappingProtocol = MappingProtocol.None;
externalIPFromRouter = true;
// Set all mappings to be mapped with their local port number being the external one
lock (portMappings)
{
for (int i = 0; i < portMappings.Count; i++)
{
PortMapping pm = (PortMapping)portMappings[i];
pm.SetExternalPort(pm.LocalPort);
pm.SetMappingStatus(PortMappingStatus.Mapped);
}
}
OnDidFinishSearchForRouter();
}
}
else
{
//IPAddress.TryParse(ExternalIPAddressFromService, out externalIPAddress);
externalIPAddress = ExternalIPAddressFromService;
externalIPFromRouter = false;
OnDidFinishSearchForRouter();
}
}
else
{
//IPAddress.TryParse(ExternalIPAddressFromService, out externalIPAddress);
externalIPAddress = ExternalIPAddressFromService;
externalIPFromRouter = false;
OnDidFinishSearchForRouter();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Private API
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// Updates the routerIPAddress variable.
///
private void UpdateRouterIPAddress()
{
routerIPAddress = null;
NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
bool found = false;
for (int i = 0; i < networkInterfaces.Length && !found; i++)
{
NetworkInterface networkInterface = networkInterfaces[i];
if (networkInterface.OperationalStatus == OperationalStatus.Up)
{
GatewayIPAddressInformationCollection gateways;
gateways = networkInterface.GetIPProperties().GatewayAddresses;
for (int j = 0; j < gateways.Count && !found; j++)
{
GatewayIPAddressInformation gatewayInfo = gateways[j];
if (gatewayInfo.Address.AddressFamily == AddressFamily.InterNetwork)
{
if (!gatewayInfo.Address.Equals(IPAddress.Any))
{
routerIPAddress = gatewayInfo.Address;
found = true;
}
}
}
}
}
}
///
/// Updates the routerManufacturer variable.
/// This is done by getting the MAC address of the router,
/// and then looking up the corresponding manufacturer in the OUI list.
///
/// The routerIPAddress variable should be set prior to calling this method.
///
private void UpdateRouterManufacturer()
{
DebugLog.WriteLine("UpdateRouterManufacturer()");
routerManufacturer = "Unknown";
if (routerIPAddress == null)
{
return;
}
Exception e;
PhysicalAddress routerMac = GetHardwareAddressForIPv4Address(routerIPAddress, out e);
if (routerMac == null)
{
DebugLog.WriteLine("PortMapper: Error getting router mac address: {0}", e);
return;
}
String result = GetManufacturerForHardwareAddress(routerMac, out e);
if (result == null)
{
if (e == null)
DebugLog.WriteLine("PortMapper: Router MAC address not in OUI list");
else
DebugLog.WriteLine("PortMapper: Error getting router manufacturer: {0}", e);
}
else
{
routerManufacturer = result;
}
}
///
/// Updates the localIPAddress variable, and the related localIPOnRouterSubnet variable.
///
/// The routerIPAddress variable should be set prior to calling this method.
///
private void UpdateLocalIPAddress()
{
localIPAddress = null;
IPHostEntry localhost = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in localhost.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
localIPAddress = ip;
break;
}
}
if (routerIPAddress != null)
{
localIPOnRouterSubnet = IsIPv4AddressInPrivateSubnet(localIPAddress);
}
else
{
localIPOnRouterSubnet = false;
}
}
///
/// Registers for notifications of power and network events.
///
private void AddSystemEventDelegates()
{
try
{
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
}
catch (Exception e)
{
// I have no idea why this throws exceptions on some computers.
// As a windows developer, it doesn't really surprise me though.
DebugLog.WriteLine("PortMapper: AddSystemEventDelegates: {0}", e);
}
}
///
/// Unregisters for notifications of power and network events.
///
private void RemoveSystemEventDelegates()
{
try
{
SystemEvents.PowerModeChanged -= new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
NetworkChange.NetworkAddressChanged -= new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);
NetworkChange.NetworkAvailabilityChanged -= new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
}
catch (Exception e)
{
// I have no idea why this throws exceptions on some computers.
// As a windows developer, it doesn't really surprise me though.
DebugLog.WriteLine("PortMapper: RemoveSystemEventDelegates: {0}", e);
}
}
private void AddNATPMPDelegates()
{
natpmpPortMapper.DidBeginWorking += new NATPMPPortMapper.PMDidBeginWorking(natpmpPortMapper_DidBeginWorking);
natpmpPortMapper.DidEndWorking += new NATPMPPortMapper.PMDidEndWorking(natpmpPortMapper_DidEndWorking);
natpmpPortMapper.DidGetExternalIPAddress += new NATPMPPortMapper.PMDidGetExternalIPAddress(natpmpPortMapper_DidGetExternalIPAddress);
natpmpPortMapper.DidFail += new NATPMPPortMapper.PMDidFail(natpmpPortMapper_DidFail);
natpmpPortMapper.DidReceiveBroadcastExternalIPChange += new NATPMPPortMapper.PMDidReceiveBroadcastExternalIPChange(natpmpPortMapper_DidReceiveBroadcastExternalIPChange);
}
private void RemoveNATPMPDelegates()
{
natpmpPortMapper.DidBeginWorking -= new NATPMPPortMapper.PMDidBeginWorking(natpmpPortMapper_DidBeginWorking);
natpmpPortMapper.DidEndWorking -= new NATPMPPortMapper.PMDidEndWorking(natpmpPortMapper_DidEndWorking);
natpmpPortMapper.DidGetExternalIPAddress -= new NATPMPPortMapper.PMDidGetExternalIPAddress(natpmpPortMapper_DidGetExternalIPAddress);
natpmpPortMapper.DidFail -= new NATPMPPortMapper.PMDidFail(natpmpPortMapper_DidFail);
natpmpPortMapper.DidReceiveBroadcastExternalIPChange -= new NATPMPPortMapper.PMDidReceiveBroadcastExternalIPChange(natpmpPortMapper_DidReceiveBroadcastExternalIPChange);
}
private void AddUPnPDelegates()
{
upnpPortMapper.DidBeginWorking += new UPnPPortMapper.PMDidBeginWorking(upnpPortMapper_DidBeginWorking);
upnpPortMapper.DidEndWorking += new UPnPPortMapper.PMDidEndWorking(upnpPortMapper_DidEndWorking);
upnpPortMapper.DidGetExternalIPAddress += new UPnPPortMapper.PMDidGetExternalIPAddress(upnpPortMapper_DidGetExternalIPAddress);
upnpPortMapper.DidFail += new UPnPPortMapper.PMDidFail(upnpPortMapper_DidFail);
}
private void RemoveUPnPDelegates()
{
upnpPortMapper.DidBeginWorking -= new UPnPPortMapper.PMDidBeginWorking(upnpPortMapper_DidBeginWorking);
upnpPortMapper.DidEndWorking -= new UPnPPortMapper.PMDidEndWorking(upnpPortMapper_DidEndWorking);
upnpPortMapper.DidGetExternalIPAddress -= new UPnPPortMapper.PMDidGetExternalIPAddress(upnpPortMapper_DidGetExternalIPAddress);
upnpPortMapper.DidFail -= new UPnPPortMapper.PMDidFail(upnpPortMapper_DidFail);
}
private void UpdatePortMappings()
{
// This method is called from either AddPortMapping or RemovePortMapping
if (mappingProtocol == MappingProtocol.NATPMP)
{
natpmpPortMapper.UpdatePortMappings();
}
else if (mappingProtocol == MappingProtocol.UPnP)
{
upnpPortMapper.UpdatePortMappings();
}
}
///
/// Called from:
/// - RefreshThread
/// - UpdateLocalIPAddress - RefreshThread
/// - GetRouterPhysicalAddress - GetRouterManufacturer - RefreshThread
///
/// - RouterIPAddress property
///
/// - natpmpPortMapper_DidReceiveBroadcastExternalIPChange
///
///
// private IPAddress GetRouterIPAddress()
// {
// UInt32 routerAddr = 0;
// if (NATPMP.getdefaultgateway(ref routerAddr) < 0)
// {
// DebugLog.WriteLine("PortMapper: Unable to get router ip address");
// return null;
// }
//
// try
// {
// return new IPAddress((long)routerAddr);
// }
// catch (Exception e)
// {
// DebugLog.WriteLine("PortMapper: Unable to get router ip address: {0}", e);
// return null;
// }
// }
private void IncreaseWorkCount()
{
if (workCount == 0)
{
OnDidStartWork();
}
workCount++;
}
private void DecreaseWorkCount()
{
workCount--;
if (workCount == 0)
{
if (upnpStatus == MappingStatus.Works && requestedUPnPMappingTable)
{
OnDidReceiveUPNPMappingTable(upnpPortMapper.ExistingUPnPPortMappings);
requestedUPnPMappingTable = false;
}
OnDidFinishWork();
}
}
private void DecreaseWorkCount(Object state)
{
// Called via timer (on a background thread)
DecreaseWorkCount();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region NATPMPPortMapper Delegate Methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void natpmpPortMapper_DidBeginWorking(NATPMPPortMapper sender)
{
IncreaseWorkCount();
}
private void natpmpPortMapper_DidEndWorking(NATPMPPortMapper sender)
{
DecreaseWorkCount();
}
private void natpmpPortMapper_DidGetExternalIPAddress(NATPMPPortMapper sender, System.Net.IPAddress ip)
{
bool shouldNotify = false;
if (natpmpStatus == MappingStatus.Trying)
{
natpmpStatus = MappingStatus.Works;
mappingProtocol = MappingProtocol.NATPMP;
shouldNotify = true;
}
externalIPAddress = ip;
OnExternalIPAddressDidChange();
//Using Hard external ip (from service) address - this is the external ip address of the actual server
Logger.Info("Check to see if it's safe to proceed with port mapping. Router External IP: {0}, Server External IP: {1}", externalIPAddress.ToString(), ExternalIPAddressFromService.ToString());
if (!String.IsNullOrEmpty(ExternalIPAddressFromService.ToString()))
{
_isSafeForPortMapping = (externalIPAddress.ToString() == ExternalIPAddressFromService.ToString() ? true : false);
Logger.Info("Safe to proceed with port mapping.");
}
else
{
//If external ip is incorrect or can't be retrieved, then we assume port mapping is not safe.
Logger.Info("Unsafe to proceed with port mapping.");
_isSafeForPortMapping = false;
}
if (shouldNotify)
{
OnDidFinishSearchForRouter();
}
//OnCanAddPortMapping();
}
private void natpmpPortMapper_DidFail(NATPMPPortMapper sender)
{
DebugLog.WriteLine("natpmpPortMapper_DidFail");
if (natpmpStatus == MappingStatus.Trying)
{
natpmpStatus = MappingStatus.Failed;
}
else if (natpmpStatus == MappingStatus.Works)
{
externalIPAddress = null;
}
if (upnpStatus == MappingStatus.Failed)
{
OnDidFinishSearchForRouter();
}
}
private void natpmpPortMapper_DidReceiveBroadcastExternalIPChange(NATPMPPortMapper sender, IPAddress ip, IPAddress senderIP)
{
if (isRunning)
{
DebugLog.WriteLine("natpmpPortMapper_DidReceiveBroadcastExternalIPChange");
if (senderIP == localIPAddress)
{
DebugLog.WriteLine("Refreshing because of NAT-PMP device external IP broadcast");
Refresh();
}
else
{
DebugLog.WriteLine("Got information from rogue NAT-PMP device");
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region UPnPPortMapper Delegate Methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void upnpPortMapper_DidBeginWorking(UPnPPortMapper sender)
{
IncreaseWorkCount();
}
private void upnpPortMapper_DidEndWorking(UPnPPortMapper sender)
{
DecreaseWorkCount();
}
private void upnpPortMapper_DidGetExternalIPAddress(UPnPPortMapper sender, IPAddress ip, bool isFromRouter)
{
DebugLog.WriteLine("upnpPortMapper_DidGetExternalIPAddress: {0}", ip);
bool shouldNotify = false;
if (upnpStatus == MappingStatus.Trying)
{
upnpStatus = MappingStatus.Works;
mappingProtocol = MappingProtocol.UPnP;
shouldNotify = true;
}
externalIPAddress = ip;
externalIPFromRouter = isFromRouter;
OnExternalIPAddressDidChange();
//Using Hard external ip (from service) address - this is the external ip address of the actual server
Logger.Info("Check to see if it's safe to proceed with port mapping. Router External IP: {0}, Server External IP: {1}", externalIPAddress.ToString(), ExternalIPAddressFromService.ToString());
if (!String.IsNullOrEmpty(ExternalIPAddressFromService.ToString()))
{
_isSafeForPortMapping = (externalIPAddress.ToString() == ExternalIPAddressFromService.ToString() ? true : false);
Logger.Info("Safe to proceed with port mapping.");
}
else
{
//If external ip is incorrect or can't be retrieved, then we assume port mapping is not safe.
Logger.Info("Unsafe to proceed with port mapping.");
_isSafeForPortMapping = false;
}
if (upnpStatus == MappingStatus.Works)
{
//Get Existing Mappings
RequestUPnPMappingTable();
}
if (shouldNotify)
{
OnDidFinishSearchForRouter();
}
//OnCanAddPortMapping();
}
private void upnpPortMapper_DidFail(UPnPPortMapper sender)
{
DebugLog.WriteLine("upnpPortMapper_DidFail");
if (upnpStatus == MappingStatus.Trying)
{
upnpStatus = MappingStatus.Failed;
}
else if (upnpStatus == MappingStatus.Works)
{
externalIPAddress = null;
}
if (natpmpStatus == MappingStatus.Failed)
{
OnDidFinishSearchForRouter();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region SystemEvent Delegate Methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
if (e.Mode == PowerModes.Suspend)
{
// System is going to sleep
isGoingToSleep = true;
if (isRunning)
{
if (natpmpStatus == MappingStatus.Works)
{
natpmpPortMapper.StopBlocking();
}
if (upnpStatus == MappingStatus.Works)
{
upnpPortMapper.StopBlocking();
}
}
}
else if (e.Mode == PowerModes.Resume)
{
// System is waking up (but may not have an internet connection yet)
// Wait for network information to refresh
isGoingToSleep = false;
}
}
private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
isNetworkAvailable = e.IsAvailable;
}
private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
if (!isGoingToSleep)
{
if (isNetworkAvailable)
{
DebugLog.WriteLine("Refreshing because of network change");
Refresh();
}
else
{
// Ignore - System does not yet have network restored
}
}
else
{
// Ignore - System is going to sleep
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Utility Methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private bool IsIPv4AddressInPrivateSubnet(IPAddress myAddr)
{
if (myAddr == null) return false;
// Private subnets as defined in http://tools.ietf.org/html/rfc1918
// Loopback address 127.0.0.1/8 http://tools.ietf.org/html/rfc3330
// Zeroconf/bonjour self assigned addresses 169.254.0.0/16 http://tools.ietf.org/html/rfc3927
String[] netAddrs = { "192.168.0.0", "10.0.0.0", "172.16.0.0", "127.0.0.1", "169.254.0.0" };
String[] netMasks = { "255.255.0.0", "255.0.0.0", "255.240.0.0", "255.0.0.0", "255.255.0.0" };
UInt32 myIP = BitConverter.ToUInt32(myAddr.GetAddressBytes(), 0);
for (int i = 0; i < netMasks.Length; i++)
{
IPAddress netAddr = IPAddress.Parse(netAddrs[i]);
UInt32 netIP = BitConverter.ToUInt32(netAddr.GetAddressBytes(), 0);
IPAddress maskAddr = IPAddress.Parse(netMasks[i]);
UInt32 maskIP = BitConverter.ToUInt32(maskAddr.GetAddressBytes(), 0);
if ((myIP & maskIP) == (netIP & maskIP))
{
return true;
}
}
return false;
}
///
/// Only called from GetRouterPhysicalAddress()
///
///
///
///
private PhysicalAddress GetHardwareAddressForIPv4Address(IPAddress ip, out Exception e)
{
if (ip == null)
{
e = new ArgumentNullException();
return null;
}
if (ip.AddressFamily != AddressFamily.InterNetwork)
{
e = new ArgumentException("Only supports IPv4 addresses");
return null;
}
UInt32 dstAddrInt = BitConverter.ToUInt32(ip.GetAddressBytes(), 0);
UInt32 srcAddrInt = 0;
byte[] mac = new byte[6]; // 48 bit
int length = mac.Length;
int reply = Win32.SendARP(dstAddrInt, srcAddrInt, mac, ref length);
if (reply != 0)
{
e = new System.ComponentModel.Win32Exception(reply);
return null;
}
e = null;
return new PhysicalAddress(mac);
}
///
/// Searches for a match for the given mac address in the oui.txt file.
/// If a match is found, the corresponding company name is returned.
/// If a match is not found, null is returned.
/// If an error occurs, null is returned, and the exception is set.
///
/// Note: The oui list may contain missing names, so an empty string may be returned.
///
private String GetManufacturerForHardwareAddress(PhysicalAddress macAddr, out Exception e)
{
if (macAddr == null)
{
e = new ArgumentNullException();
return null;
}
String macAddrPrefix = macAddr.ToString().Substring(0, 6);
StreamReader streamReader = null;
String result = null;
try
{
// OUI - Organizationally Unique Identifier
//
// If you wish to update the list of OUI's, you can get the latest version here:
// http://standards.ieee.org/regauth/oui/index.shtml
// Then format the list using the ReformatOUI method below.
// Ensure that the oui file is in UTF-8.
//streamReader = File.OpenText("oui.txt");
streamReader = File.OpenText("ouiformatted.txt");
String line = streamReader.ReadLine();
while ((line != null) && (result == null))
{
if (line.StartsWith(macAddrPrefix))
{
result = line.Substring(6).Trim();
}
line = streamReader.ReadLine();
}
}
catch (Exception ex)
{
e = ex;
}
finally
{
if (streamReader != null) streamReader.Close();
}
e = null;
return result;
}
///
/// This method is not used in the framework, but may be used by developers to properly
/// format the most recently obtained list of OUI's.
/// The list can be obtained from IEEE:
/// http://standards.ieee.org/regauth/oui/index.shtml
///
///
/// Path to downloaded oui text file.
/// This file should be encoded in UTF-8 format.
///
///
/// Path to output formatted out file.
/// This file will be created or overwritten.
///
private void ReformatOUI(String inFilePath, String outFilePath)
{
StreamReader streamReader = File.OpenText(inFilePath);
String line = streamReader.ReadLine();
StreamWriter streamWriter = File.CreateText(outFilePath);
uint lineCount = 0;
uint badLineCount = 0;
while (line != null)
{
if (line.Contains("(base 16)"))
{
String[] separators = { "(base 16)" };
String[] tokens = line.Split(separators, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length == 2)
{
String mac = tokens[0].Trim();
String company = tokens[1].Trim();
String outLine = mac + " " + company;
lineCount++;
streamWriter.WriteLine(outLine);
}
else
{
badLineCount++;
}
}
line = streamReader.ReadLine();
}
streamReader.Close();
streamWriter.Close();
Console.WriteLine("Number of lines: {0}", lineCount);
Console.WriteLine("Number of bad lines: {0}", badLineCount);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
}