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 /////////////////////////////////////////////////////////////////////////////////////////////////////////////// } }