Initial Commit

This commit is contained in:
2016-07-27 00:32:34 -04:00
commit 8d162b2035
701 changed files with 188672 additions and 0 deletions

View File

@@ -0,0 +1,569 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
namespace TCMPortMapper
{
class NATPMPPortMapper
{
public delegate void PMDidFail(NATPMPPortMapper sender);
public delegate void PMDidGetExternalIPAddress(NATPMPPortMapper sender, IPAddress ip);
public delegate void PMDidBeginWorking(NATPMPPortMapper sender);
public delegate void PMDidEndWorking(NATPMPPortMapper sender);
public delegate void PMDidReceiveBroadcastExternalIPChange(NATPMPPortMapper sender, IPAddress ip, IPAddress senderIP);
public event PMDidFail DidFail;
public event PMDidGetExternalIPAddress DidGetExternalIPAddress;
public event PMDidBeginWorking DidBeginWorking;
public event PMDidEndWorking DidEndWorking;
public event PMDidReceiveBroadcastExternalIPChange DidReceiveBroadcastExternalIPChange;
private Object multiThreadLock = new Object();
private Object singleThreadLock = new Object();
private volatile ThreadID threadID;
private volatile ThreadFlags refreshExternalIPThreadFlags;
private volatile ThreadFlags updatePortMappingsThreadFlags;
private Timer updateTimer;
private uint updateInterval;
private UdpClient udpClient;
private IPAddress lastBroadcastExternalIP;
private enum ThreadID
{
None = 0,
RefreshExternalIP = 1,
UpdatePortMappings = 2
}
[Flags]
private enum ThreadFlags
{
None = 0,
ShouldQuit = 1,
ShouldRestart = 2
}
// Standard routine:
//
// Refresh -> triggers RefreshExternalIPThread -> Upon completion of thread, UpdatePortMappings is called.
// Case 1: No threads are running -> perfect, trigger thread as planned
// Case 2: RefreshExternalIPThread is running -> this thread is aborted, and then Refresh is called again
// Case 3: UpdatePortMappingsThread is running -> this thread is aborted, and then Refresh is called again
//
// UpdatePortMappings -> triggers UpdatePortMappingsThread -> Upon completion of thread, AdjustUpdateTimer is called
// Case 1: No threads are running -> perfect, trigger thread as planned
// Case 2: RefreshExternalIPInThread is running -> That's fine, we do nothing
// Case 3: UpdatePortMappingsInThread is running -> this thread is aborted, and restarted from beginning
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Public API
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
public NATPMPPortMapper()
{
// Until I find a way around this bug, there's no reason to setup the udp client...
// Add UDP listener for public ip update packets
// udpClient = new UdpClient(5351);
// Note: The following code throws an exception for some reason.
// The JoinMulticastGroup works fine for every multicast address except 224.0.0.1
// Another reason why windows sucks.
// udpClient.JoinMulticastGroup(IPAddress.Parse("224.0.0.1"));
// So basically, the udpClient won't be receiving anything.
// I consider this to be a bug in Windows and/or .Net.
// udpClient.BeginReceive(new AsyncCallback(udpClient_DidReceive), null);
}
public void Refresh()
{
updateInterval = 3600 / 2;
refreshExternalIPThreadFlags = ThreadFlags.None;
updatePortMappingsThreadFlags = ThreadFlags.None;
//threadID = ThreadID.RefreshExternalIP;
RefreshExternalIPThread();
//if (threadID == ThreadID.RefreshExternalIP)
//{
// refreshExternalIPThreadFlags = ThreadFlags.ShouldQuit | ThreadFlags.ShouldRestart;
//}
//else if (threadID == ThreadID.UpdatePortMappings)
//{
// updatePortMappingsThreadFlags = ThreadFlags.ShouldQuit;
//}
}
public void UpdatePortMappings()
{
updatePortMappingsThreadFlags = ThreadFlags.None;
//threadID = ThreadID.UpdatePortMappings;
UpdatePortMappingsThread();
//if (threadID == ThreadID.UpdatePortMappings)
//{
// updatePortMappingsThreadFlags = ThreadFlags.ShouldQuit | ThreadFlags.ShouldRestart;
//}
}
public void Stop()
{
if (updateTimer != null)
{
updateTimer.Dispose();
updateTimer = null;
}
// Restart update to remove mappings before stopping
UpdatePortMappings();
}
public void StopBlocking()
{
refreshExternalIPThreadFlags = ThreadFlags.ShouldQuit;
updatePortMappingsThreadFlags = ThreadFlags.ShouldQuit;
NATPMP.natpmp_t natpmp = new NATPMP.natpmp_t();
NATPMP.initnatpmp(ref natpmp);
List<PortMapping> mappingsToRemove = PortMapper.SharedInstance.PortMappingsToRemove;
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;
for (int i = 0; i < mappingsToStop.Count; i++)
{
PortMapping pm = mappingsToStop[i];
if (pm.MappingStatus == PortMappingStatus.Mapped)
{
RemovePortMapping(pm, ref natpmp);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Delegate Methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected virtual void OnDidFail()
{
if (DidFail != null)
{
PortMapper.SharedInstance.Invoke(DidFail, this);
}
}
protected virtual void OnDidGetExternalIPAddress(IPAddress ip)
{
if (DidGetExternalIPAddress != null)
{
DidGetExternalIPAddress(this, ip);
//PortMapper.SharedInstance.Invoke(DidGetExternalIPAddress, this, ip);
}
}
protected virtual void OnDidBeginWorking()
{
if (DidBeginWorking != null)
{
// This is thread safe, so there's no need to Invoke it
DidBeginWorking(this);
}
}
protected virtual void OnDidEndWorking()
{
if (DidEndWorking != null)
{
// This is thread safe, so there's no need to Invoke it
DidEndWorking(this);
}
}
protected virtual void OnDidReceiveBroadcastExternalIPChange(IPAddress externalIP, IPAddress senderIP)
{
if (lastBroadcastExternalIP == null)
{
lastBroadcastExternalIP = externalIP;
}
else
{
if (lastBroadcastExternalIP == externalIP)
{
// To accommodate packet loss, the NAT-PMP protocol may broadcast
// an external IP address change up to 10 times.
// We only need to broadcast it once.
return;
}
}
if (DidReceiveBroadcastExternalIPChange != null)
{
DidReceiveBroadcastExternalIPChange(this, externalIP, senderIP);
//PortMapper.SharedInstance.Invoke(DidReceiveBroadcastExternalIPChange, this, externalIP, senderIP);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Private API
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private bool AddPortMapping(PortMapping portMapping, ref NATPMP.natpmp_t natpmp)
{
return ApplyPortMapping(portMapping, false, ref natpmp);
}
private bool RefreshPortMapping(PortMapping portMapping, ref NATPMP.natpmp_t natpmp)
{
return ApplyPortMapping(portMapping, false, ref natpmp);
}
private bool RemovePortMapping(PortMapping portMapping, ref NATPMP.natpmp_t natpmp)
{
return ApplyPortMapping(portMapping, true, 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 Refresh External IP Thread
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void RefreshExternalIPThread()
{
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)");
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);
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();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region Update Port Mappings Thread
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void UpdatePortMappingsThread()
{
OnDidBeginWorking();
NATPMP.natpmp_t natpmp = new NATPMP.natpmp_t();
NATPMP.initnatpmp(ref natpmp);
// Remove mappings scheduled for removal
List<PortMapping> mappingsToRemove = PortMapper.SharedInstance.PortMappingsToRemove;
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;
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);
if (PortMapper.SharedInstance.IsRunning)
{
if ((updatePortMappingsThreadFlags & ThreadFlags.ShouldRestart) > 0)
{
UpdatePortMappings();
}
else if ((updatePortMappingsThreadFlags & ThreadFlags.ShouldQuit) > 0)
{
Refresh();
}
else
{
AdjustUpdateTimer();
}
}
OnDidEndWorking();
}
private void AdjustUpdateTimer()
{
if (updateTimer != null)
{
updateTimer.Dispose();
updateTimer = null;
}
updateTimer = new Timer(new TimerCallback(UpdatePortMappings), null, (updateInterval * 1000), Timeout.Infinite);
}
private void UpdatePortMappings(object state)
{
// Called via timer (on background thread)
UpdatePortMappings();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void udpClient_DidReceive(IAsyncResult ar)
{
// When the public address changes, the NAT gateway will send a notification on the
// multicast group 224.0.0.1 port 5351 with the format of a public address response.
//
// Public address response:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Vers = 0 | OP = 128 + 0 | Result Code |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Seconds Since Start of Epoch |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Public IPv4 Address (a.b.c.d) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
DebugLog.WriteLine("NAT-PMP: udpClient_DidReceive");
try
{
IPEndPoint ep = new IPEndPoint(IPAddress.Parse("224.0.0.1"), 5351);
byte[] data = udpClient.EndReceive(ar, ref ep);
if (data.Length == 12)
{
byte[] rawIP = new byte[4];
Buffer.BlockCopy(data, 8, rawIP, 0, 4);
IPAddress newIP = new IPAddress(rawIP);
OnDidReceiveBroadcastExternalIPChange(newIP, ep.Address);
}
}
catch (Exception e)
{
DebugLog.WriteLine("NAT-PMP: udpClient_DidReceive: Exception: {0}", e);
}
udpClient.BeginReceive(new AsyncCallback(udpClient_DidReceive), null);
}
}
}