public void StopBlocking()
        {
            // 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)
            {
                refreshExternalIPThreadFlags  = ThreadFlags.ShouldQuit;
                updatePortMappingsThreadFlags = ThreadFlags.ShouldQuit;

                Monitor.Enter(multiThreadLock);

                NATPMP.natpmp_t natpmp = new NATPMP.natpmp_t();
                NATPMP.initnatpmp(ref natpmp);

                List <PortMapping> mappingsToRemove = PortMapper.SharedInstance.PortMappingsToRemove;
                lock (mappingsToRemove)
                {
                    while (mappingsToRemove.Count > 0)
                    {
                        PortMapping pm = mappingsToRemove[0];

                        if (pm.MappingStatus == PortMappingStatus.Mapped)
                        {
                            RemovePortMapping(pm, ref natpmp);
                        }

                        mappingsToRemove.RemoveAt(0);
                    }
                }

                List <PortMapping> mappingsToStop = PortMapper.SharedInstance.PortMappings;
                lock (mappingsToStop)
                {
                    for (int i = 0; i < mappingsToStop.Count; i++)
                    {
                        PortMapping pm = mappingsToStop[i];

                        if (pm.MappingStatus == PortMappingStatus.Mapped)
                        {
                            RemovePortMapping(pm, ref natpmp);
                        }
                    }
                }

                Monitor.Exit(multiThreadLock);
            }
        }
 private bool RemovePortMapping(PortMapping portMapping, ref NATPMP.natpmp_t natpmp)
 {
     return ApplyPortMapping(portMapping, true, ref natpmp);
 }
 private bool RefreshPortMapping(PortMapping portMapping, ref NATPMP.natpmp_t natpmp)
 {
     return ApplyPortMapping(portMapping, false, ref natpmp);
 }
        private bool ApplyPortMapping(PortMapping portMapping, bool remove, ref NATPMP.natpmp_t natpmp)
        {
            NATPMP.natpmpresp_t response = new NATPMP.natpmpresp_t();
            int r;
            Win32.TimeValue timeout = new Win32.TimeValue();
            Win32.FileDescriptorSet fds = new Win32.FileDescriptorSet(1);

            if (!remove)
            {
                portMapping.SetMappingStatus(PortMappingStatus.Trying);
            }
            PortMappingTransportProtocol protocol = portMapping.TransportProtocol;

            for (int i = 1; i <= 2; i++)
            {
                PortMappingTransportProtocol currentProtocol;
                if(i == 1)
                    currentProtocol = PortMappingTransportProtocol.UDP;
                else
                    currentProtocol = PortMappingTransportProtocol.TCP;

                if (protocol == currentProtocol || protocol == PortMappingTransportProtocol.Both)
                {
                    r = NATPMP.sendnewportmappingrequest(ref natpmp,
                        (i == 1) ? NATPMP.PROTOCOL_UDP : NATPMP.PROTOCOL_TCP,
                        portMapping.LocalPort, portMapping.DesiredExternalPort, (uint)(remove ? 0 : 3600));

                    do
                    {
                        fds.Count = 1;
                        fds.Array[0] = (IntPtr)natpmp.s;
                        NATPMP.getnatpmprequesttimeout(ref natpmp, ref timeout);

                        Win32.select(0, ref fds, IntPtr.Zero, IntPtr.Zero, ref timeout);

                        r = NATPMP.readnatpmpresponseorretry(ref natpmp, ref response);
                    }
                    while(r == NATPMP.ERR_TRYAGAIN);

                    if (r < 0)
                    {
                        portMapping.SetMappingStatus(PortMappingStatus.Unmapped);
                        return false;
                    }
                }
            }

            if (remove)
            {
                portMapping.SetMappingStatus(PortMappingStatus.Unmapped);
            }
            else
            {
                updateInterval = Math.Min(updateInterval, response.pnu_newportmapping.lifetime / 2);
                if (updateInterval < 60)
                {
                    DebugLog.WriteLine("NAT-PMP: ApplyPortMapping: Caution - new port mapping had a lifetime < 120 ({0})",
                        response.pnu_newportmapping.lifetime);

                    updateInterval = 60;
                }
                portMapping.SetExternalPort(response.pnu_newportmapping.mappedpublicport);
                portMapping.SetMappingStatus(PortMappingStatus.Mapped);
            }

            return true;
        }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Update Port Mappings Thread
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void UpdatePortMappingsThread()
        {
            Monitor.Enter(multiThreadLock);
            OnDidBeginWorking();

            NATPMP.natpmp_t natpmp = new NATPMP.natpmp_t();
            NATPMP.initnatpmp(ref natpmp);

            // Remove mappings scheduled for removal

            List <PortMapping> mappingsToRemove = PortMapper.SharedInstance.PortMappingsToRemove;

            lock (mappingsToRemove)
            {
                while ((mappingsToRemove.Count > 0) && (updatePortMappingsThreadFlags == ThreadFlags.None))
                {
                    PortMapping mappingToRemove = mappingsToRemove[0];

                    if (mappingToRemove.MappingStatus == PortMappingStatus.Mapped)
                    {
                        RemovePortMapping(mappingToRemove, ref natpmp);
                    }

                    mappingsToRemove.RemoveAt(0);
                }
            }

            // If the port mapper is running:
            //   -Refresh existing mappings
            //   -Add new mappings
            // If the port mapper is stopped:
            //   -Remove any existing mappings

            List <PortMapping> mappings = PortMapper.SharedInstance.PortMappings;

            lock (mappings)
            {
                for (int i = 0; i < mappings.Count && updatePortMappingsThreadFlags == ThreadFlags.None; i++)
                {
                    PortMapping existingMapping = mappings[i];
                    bool        isRunning       = PortMapper.SharedInstance.IsRunning;

                    if (existingMapping.MappingStatus == PortMappingStatus.Mapped)
                    {
                        if (isRunning)
                        {
                            RefreshPortMapping(existingMapping, ref natpmp);
                        }
                        else
                        {
                            RemovePortMapping(existingMapping, ref natpmp);
                        }
                    }
                }

                for (int i = 0; i < mappings.Count && updatePortMappingsThreadFlags == ThreadFlags.None; i++)
                {
                    PortMapping mappingToAdd = mappings[i];
                    bool        isRunning    = PortMapper.SharedInstance.IsRunning;

                    if (mappingToAdd.MappingStatus == PortMappingStatus.Unmapped && isRunning)
                    {
                        AddPortMapping(mappingToAdd, ref natpmp);
                    }
                }
            }

            NATPMP.closenatpmp(ref natpmp);
            Monitor.Exit(multiThreadLock);

            if (PortMapper.SharedInstance.IsRunning)
            {
                if ((updatePortMappingsThreadFlags & ThreadFlags.ShouldRestart) > 0)
                {
                    UpdatePortMappings();
                }
                else if ((updatePortMappingsThreadFlags & ThreadFlags.ShouldQuit) > 0)
                {
                    Refresh();
                }
                else
                {
                    AdjustUpdateTimer();
                }
            }

            OnDidEndWorking();
        }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Refresh External IP Thread
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void RefreshExternalIPThread()
        {
            Monitor.Enter(multiThreadLock);
            OnDidBeginWorking();

            NATPMP.natpmp_t     natpmp   = new NATPMP.natpmp_t();
            NATPMP.natpmpresp_t response = new NATPMP.natpmpresp_t();
            int r;

            Win32.TimeValue         timeout = new Win32.TimeValue();
            Win32.FileDescriptorSet fds     = new Win32.FileDescriptorSet(1);
            bool didFail = false;

            r = NATPMP.initnatpmp(ref natpmp);
            if (r < 0)
            {
                didFail = true;
            }
            else
            {
                r = NATPMP.sendpublicaddressrequest(ref natpmp);
                if (r < 0)
                {
                    didFail = true;
                }
                else
                {
                    do
                    {
                        fds.Count    = 1;
                        fds.Array[0] = (IntPtr)natpmp.s;
                        NATPMP.getnatpmprequesttimeout(ref natpmp, ref timeout);

                        Win32.select(0, ref fds, IntPtr.Zero, IntPtr.Zero, ref timeout);

                        r = NATPMP.readnatpmpresponseorretry(ref natpmp, ref response);
                        if (refreshExternalIPThreadFlags != ThreadFlags.None)
                        {
                            DebugLog.WriteLine("NAT-PMP: RefreshExternalIPThread quit prematurely (1)");

                            Monitor.Exit(multiThreadLock);
                            if ((refreshExternalIPThreadFlags & ThreadFlags.ShouldRestart) > 0)
                            {
                                Refresh();
                            }
                            NATPMP.closenatpmp(ref natpmp);
                            OnDidEndWorking();
                            return;
                        }
                    }while (r == NATPMP.ERR_TRYAGAIN);

                    if (r < 0)
                    {
                        didFail = true;
                        DebugLog.WriteLine("NAT-PMP: IP refresh did time out");
                    }
                    else
                    {
                        IPAddress ipaddr = new IPAddress((long)response.pnu_publicaddress.addr);
                        OnDidGetExternalIPAddress(ipaddr);
                    }
                }
            }

            NATPMP.closenatpmp(ref natpmp);
            Monitor.Exit(multiThreadLock);

            if (refreshExternalIPThreadFlags != ThreadFlags.None)
            {
                DebugLog.WriteLine("NAT-PMP: RefreshExternalIPThread quit prematurely (2)");

                if ((refreshExternalIPThreadFlags & ThreadFlags.ShouldRestart) > 0)
                {
                    Refresh();
                }
            }
            else
            {
                if (didFail)
                {
                    OnDidFail();
                }
                else
                {
                    UpdatePortMappings();
                }
            }
            OnDidEndWorking();
        }
        private bool ApplyPortMapping(PortMapping portMapping, bool remove, ref NATPMP.natpmp_t natpmp)
        {
            NATPMP.natpmpresp_t response = new NATPMP.natpmpresp_t();
            int r;

            Win32.TimeValue         timeout = new Win32.TimeValue();
            Win32.FileDescriptorSet fds     = new Win32.FileDescriptorSet(1);

            if (!remove)
            {
                portMapping.SetMappingStatus(PortMappingStatus.Trying);
            }
            PortMappingTransportProtocol protocol = portMapping.TransportProtocol;

            for (int i = 1; i <= 2; i++)
            {
                PortMappingTransportProtocol currentProtocol;
                if (i == 1)
                {
                    currentProtocol = PortMappingTransportProtocol.UDP;
                }
                else
                {
                    currentProtocol = PortMappingTransportProtocol.TCP;
                }

                if (protocol == currentProtocol || protocol == PortMappingTransportProtocol.Both)
                {
                    r = NATPMP.sendnewportmappingrequest(ref natpmp,
                                                         (i == 1) ? NATPMP.PROTOCOL_UDP : NATPMP.PROTOCOL_TCP,
                                                         portMapping.LocalPort, portMapping.DesiredExternalPort, (uint)(remove ? 0 : 3600));

                    do
                    {
                        fds.Count    = 1;
                        fds.Array[0] = (IntPtr)natpmp.s;
                        NATPMP.getnatpmprequesttimeout(ref natpmp, ref timeout);

                        Win32.select(0, ref fds, IntPtr.Zero, IntPtr.Zero, ref timeout);

                        r = NATPMP.readnatpmpresponseorretry(ref natpmp, ref response);
                    }while(r == NATPMP.ERR_TRYAGAIN);

                    if (r < 0)
                    {
                        portMapping.SetMappingStatus(PortMappingStatus.Unmapped);
                        return(false);
                    }
                }
            }

            if (remove)
            {
                portMapping.SetMappingStatus(PortMappingStatus.Unmapped);
            }
            else
            {
                updateInterval = Math.Min(updateInterval, response.pnu_newportmapping.lifetime / 2);
                if (updateInterval < 60)
                {
                    DebugLog.WriteLine("NAT-PMP: ApplyPortMapping: Caution - new port mapping had a lifetime < 120 ({0})",
                                       response.pnu_newportmapping.lifetime);

                    updateInterval = 60;
                }
                portMapping.SetExternalPort(response.pnu_newportmapping.mappedpublicport);
                portMapping.SetMappingStatus(PortMappingStatus.Mapped);
            }

            return(true);
        }