diff --git a/Lidgren.Network/Lidgren.Network.csproj b/Lidgren.Network/Lidgren.Network.csproj
index d310e1f..86029e9 100644
--- a/Lidgren.Network/Lidgren.Network.csproj
+++ b/Lidgren.Network/Lidgren.Network.csproj
@@ -118,6 +118,7 @@
+
diff --git a/Lidgren.Network/NetPeer.Internal.cs b/Lidgren.Network/NetPeer.Internal.cs
index aff472d..b6ca9b3 100644
--- a/Lidgren.Network/NetPeer.Internal.cs
+++ b/Lidgren.Network/NetPeer.Internal.cs
@@ -22,6 +22,7 @@ namespace Lidgren.Network
private object m_initializeLock = new object();
private uint m_frameCounter;
private double m_lastHeartbeat;
+ private NetUPnP m_upnp;
internal readonly NetPeerConfiguration m_configuration;
private readonly NetQueue m_releasedIncomingMessages;
@@ -63,6 +64,9 @@ namespace Lidgren.Network
if (m_status == NetPeerStatus.Running)
return;
+ if (m_configuration.m_enableUPnP)
+ m_upnp = new NetUPnP(this);
+
InitializePools();
m_releasedIncomingMessages.Clear();
@@ -288,126 +292,147 @@ namespace Lidgren.Network
//if (m_socket == null || m_socket.Available < 1)
// return;
- int bytesReceived = 0;
- try
+ do
{
- bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
- }
- catch (SocketException sx)
- {
- if (sx.SocketErrorCode == SocketError.ConnectionReset)
- {
- // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
- // we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?!
- // So, what to do?
- return;
- }
-
- LogWarning(sx.ToString());
- return;
- }
-
- if (bytesReceived < NetConstants.HeaderByteSize)
- return;
-
- //LogVerbose("Received " + bytesReceived + " bytes");
-
- IPEndPoint ipsender = (IPEndPoint)m_senderRemote;
-
- NetConnection sender = null;
- m_connectionLookup.TryGetValue(ipsender, out sender);
-
- double receiveTime = NetTime.Now;
- //
- // parse packet into messages
- //
- int numMessages = 0;
- int ptr = 0;
- while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize)
- {
- // decode header
- // 8 bits - NetMessageType
- // 1 bit - Fragment?
- // 15 bits - Sequence number
- // 16 bits - Payload length in bits
-
- numMessages++;
-
- NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++];
-
- byte low = m_receiveBuffer[ptr++];
- byte high = m_receiveBuffer[ptr++];
-
- bool isFragment = ((low & 1) == 1);
- ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7));
-
- ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8));
- int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength);
-
- if (bytesReceived - ptr < payloadByteLength)
- {
- LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr));
- return;
- }
-
+ int bytesReceived = 0;
try
{
- NetException.Assert(tp < NetMessageType.Unused1 || tp > NetMessageType.Unused29);
-
- if (tp >= NetMessageType.LibraryError)
+ bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
+ }
+ catch (SocketException sx)
+ {
+ if (sx.SocketErrorCode == SocketError.ConnectionReset)
{
- if (sender != null)
- sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
- else
- ReceivedUnconnectedLibraryMessage(receiveTime, ipsender, tp, ptr, payloadByteLength);
+ // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
+ // we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?!
+ // So, what to do?
+ return;
}
- else
- {
- if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
- return; // dropping unconnected message since it's not enabled
- NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength);
- msg.m_isFragment = isFragment;
- msg.m_receiveTime = receiveTime;
- msg.m_sequenceNumber = sequenceNumber;
- msg.m_receivedMessageType = tp;
- msg.m_senderConnection = sender;
- msg.m_senderEndpoint = ipsender;
- msg.m_bitLength = payloadBitLength;
- Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength);
- if (sender != null)
+ LogWarning(sx.ToString());
+ return;
+ }
+
+ if (bytesReceived < NetConstants.HeaderByteSize)
+ return;
+
+ //LogVerbose("Received " + bytesReceived + " bytes");
+
+ IPEndPoint ipsender = (IPEndPoint)m_senderRemote;
+
+ if (ipsender.Port == 1900)
+ {
+ // UPnP response
+ try
+ {
+ string resp = System.Text.Encoding.ASCII.GetString(m_receiveBuffer, 0, bytesReceived);
+ if (resp.Contains("upnp:rootdevice"))
{
- if (tp == NetMessageType.Unconnected)
+ resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
+ resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
+ m_upnp.ExtractServiceUrl(resp);
+ return;
+ }
+ }
+ catch { }
+ }
+
+ NetConnection sender = null;
+ m_connectionLookup.TryGetValue(ipsender, out sender);
+
+ double receiveTime = NetTime.Now;
+ //
+ // parse packet into messages
+ //
+ int numMessages = 0;
+ int ptr = 0;
+ while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize)
+ {
+ // decode header
+ // 8 bits - NetMessageType
+ // 1 bit - Fragment?
+ // 15 bits - Sequence number
+ // 16 bits - Payload length in bits
+
+ numMessages++;
+
+ NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++];
+
+ byte low = m_receiveBuffer[ptr++];
+ byte high = m_receiveBuffer[ptr++];
+
+ bool isFragment = ((low & 1) == 1);
+ ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7));
+
+ ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8));
+ int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength);
+
+ if (bytesReceived - ptr < payloadByteLength)
+ {
+ LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr));
+ return;
+ }
+
+ try
+ {
+ NetException.Assert(tp < NetMessageType.Unused1 || tp > NetMessageType.Unused29);
+
+ if (tp >= NetMessageType.LibraryError)
+ {
+ if (sender != null)
+ sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
+ else
+ ReceivedUnconnectedLibraryMessage(receiveTime, ipsender, tp, ptr, payloadByteLength);
+ }
+ else
+ {
+ if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
+ return; // dropping unconnected message since it's not enabled
+
+ NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength);
+ msg.m_isFragment = isFragment;
+ msg.m_receiveTime = receiveTime;
+ msg.m_sequenceNumber = sequenceNumber;
+ msg.m_receivedMessageType = tp;
+ msg.m_senderConnection = sender;
+ msg.m_senderEndpoint = ipsender;
+ msg.m_bitLength = payloadBitLength;
+ Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength);
+ if (sender != null)
{
- // We're connected; but we can still send unconnected messages to this peer
- msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
- ReleaseMessage(msg);
+ if (tp == NetMessageType.Unconnected)
+ {
+ // We're connected; but we can still send unconnected messages to this peer
+ msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
+ ReleaseMessage(msg);
+ }
+ else
+ {
+ // connected application (non-library) message
+ sender.ReceivedMessage(msg);
+ }
}
else
{
- // connected application (non-library) message
- sender.ReceivedMessage(msg);
+ // at this point we know the message type is enabled
+ // unconnected application (non-library) message
+ msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
+ ReleaseMessage(msg);
}
}
- else
- {
- // at this point we know the message type is enabled
- // unconnected application (non-library) message
- msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
- ReleaseMessage(msg);
- }
}
+ catch (Exception ex)
+ {
+ LogError("Packet parsing error: " + ex.Message + " from " + ipsender);
+ }
+ ptr += payloadByteLength;
}
- catch (Exception ex)
- {
- LogError("Packet parsing error: " + ex.Message + " from " + ipsender);
- }
- ptr += payloadByteLength;
- }
- m_statistics.PacketReceived(bytesReceived, numMessages);
- if (sender != null)
- sender.m_statistics.PacketReceived(bytesReceived, numMessages);
+ m_statistics.PacketReceived(bytesReceived, numMessages);
+ if (sender != null)
+ sender.m_statistics.PacketReceived(bytesReceived, numMessages);
+
+ } while (m_socket.Available > 0);
}
private void ReceivedUnconnectedLibraryMessage(double now, IPEndPoint senderEndpoint, NetMessageType tp, int ptr, int payloadByteLength)
diff --git a/Lidgren.Network/NetPeer.LatencySimulation.cs b/Lidgren.Network/NetPeer.LatencySimulation.cs
index 7d7e520..ae15197 100644
--- a/Lidgren.Network/NetPeer.LatencySimulation.cs
+++ b/Lidgren.Network/NetPeer.LatencySimulation.cs
@@ -115,6 +115,7 @@ namespace Lidgren.Network
connectionReset = false;
try
{
+ // TODO: refactor this check outta here
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
@@ -230,6 +231,7 @@ namespace Lidgren.Network
connectionReset = false;
try
{
+ // TODO: refactor this check outta here
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
diff --git a/Lidgren.Network/NetPeer.cs b/Lidgren.Network/NetPeer.cs
index e1ef915..210ce74 100644
--- a/Lidgren.Network/NetPeer.cs
+++ b/Lidgren.Network/NetPeer.cs
@@ -43,6 +43,11 @@ namespace Lidgren.Network
///
public int Port { get { return m_listenPort; } }
+ ///
+ /// Returns an UPnP object if enabled in the NetPeerConfiguration
+ ///
+ public NetUPnP UPnP { get { return m_upnp; } }
+
///
/// Gets or sets the application defined object containing data about the peer
///
@@ -99,7 +104,7 @@ namespace Lidgren.Network
m_handshakes = new Dictionary();
m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
m_status = NetPeerStatus.NotRunning;
- m_receivedFragmentGroups = new Dictionary>();
+ m_receivedFragmentGroups = new Dictionary>();
}
///
@@ -131,8 +136,12 @@ namespace Lidgren.Network
m_networkThread.IsBackground = true;
m_networkThread.Start();
- // allow some time for network thread to start up in case they call Connect() immediately
- Thread.Sleep(10);
+ // send upnp discovery
+ if (m_upnp != null)
+ m_upnp.Discover(this);
+
+ // allow some time for network thread to start up in case they call Connect() or UPnP calls immediately
+ Thread.Sleep(50);
}
///
@@ -258,18 +267,20 @@ namespace Lidgren.Network
}
}
-#if DEBUG
///
/// Send raw bytes; only used for debugging
///
+#if DEBUG
public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination)
- {
+#else
+ internal void RawSend(byte[] arr, int offset, int length, IPEndPoint destination)
+#endif
+ {
// wrong thread - this miiiight crash with network thread... but what's a boy to do.
Array.Copy(arr, offset, m_sendBuffer, 0, length);
bool unused;
SendPacket(length, destination, 1, out unused);
}
-#endif
///
/// Disconnects all active connections and closes the socket
diff --git a/Lidgren.Network/NetPeerConfiguration.cs b/Lidgren.Network/NetPeerConfiguration.cs
index b90bbab..524948c 100644
--- a/Lidgren.Network/NetPeerConfiguration.cs
+++ b/Lidgren.Network/NetPeerConfiguration.cs
@@ -39,6 +39,7 @@ namespace Lidgren.Network
internal float m_pingInterval;
internal bool m_useMessageRecycling;
internal float m_connectionTimeout;
+ internal bool m_enableUPnP;
internal NetIncomingMessageType m_disabledTypes;
internal int m_port;
@@ -243,6 +244,20 @@ namespace Lidgren.Network
}
}
+ ///
+ /// Enables UPnP support; enabling port forwarding and getting external ip
+ ///
+ public bool EnableUPnP
+ {
+ get { return m_enableUPnP; }
+ set
+ {
+ if (m_isLocked)
+ throw new NetException(c_isLockedMessage);
+ m_enableUPnP = value;
+ }
+ }
+
///
/// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized.
///
diff --git a/Lidgren.Network/NetUPnP.cs b/Lidgren.Network/NetUPnP.cs
new file mode 100644
index 0000000..94467eb
--- /dev/null
+++ b/Lidgren.Network/NetUPnP.cs
@@ -0,0 +1,186 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Lidgren.Network
+{
+ ///
+ /// UPnP support class
+ ///
+ public class NetUPnP
+ {
+ private string m_serviceUrl;
+ private NetPeer m_peer;
+
+ public NetUPnP(NetPeer peer)
+ {
+ m_peer = peer;
+ }
+
+ internal void Discover(NetPeer peer)
+ {
+ string str =
+"M-SEARCH * HTTP/1.1\r\n" +
+"HOST: 239.255.255.250:1900\r\n" +
+"ST:upnp:rootdevice\r\n" +
+"MAN:\"ssdp:discover\"\r\n" +
+"MX:3\r\n\r\n";
+
+ byte[] arr = System.Text.Encoding.ASCII.GetBytes(str);
+
+ peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
+ peer.RawSend(arr, 0, arr.Length, new IPEndPoint(IPAddress.Broadcast, 1900));
+ peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
+
+ // allow some extra time for router to respond
+ System.Threading.Thread.Sleep(50);
+ }
+
+ internal void ExtractServiceUrl(string resp)
+ {
+#if !DEBUG
+ try
+ {
+#endif
+ XmlDocument desc = new XmlDocument();
+ desc.Load(WebRequest.Create(resp).GetResponse().GetResponseStream());
+ XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
+ nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
+ XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
+ if (!typen.Value.Contains("InternetGatewayDevice"))
+ return;
+ XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:WANIPConnection:1\"]/tns:controlURL/text()", nsMgr);
+ if (node == null)
+ return;
+ m_serviceUrl = CombineUrls(resp, node.Value);
+ m_peer.LogDebug("UPnP service ready");
+ System.Threading.Thread.Sleep(50);
+#if !DEBUG
+ }
+ catch { return; }
+#endif
+ }
+
+ private static string CombineUrls(string gatewayURL, string subURL)
+ {
+ // Is Control URL an absolute URL?
+ if ((subURL.Contains("http:")) || (subURL.Contains(".")))
+ return subURL;
+
+ gatewayURL = gatewayURL.Replace("http://", ""); // strip any protocol
+ int n = gatewayURL.IndexOf("/");
+ if (n != -1)
+ gatewayURL = gatewayURL.Substring(0, n); // Use first portion of URL
+ return "http://" + gatewayURL + subURL;
+ }
+
+ ///
+ /// Add a forwarding rule to the router using UPnP
+ ///
+ public bool ForwardPort(int port, string description)
+ {
+ if (m_serviceUrl == null)
+ return false;
+
+ IPAddress mask;
+ var client = NetUtility.GetMyAddress(out mask);
+
+ try
+ {
+ XmlDocument xdoc = SOAPRequest(m_serviceUrl,
+ "" +
+ "" + port.ToString() + "" +
+ "" + ProtocolType.Udp.ToString().ToUpper() + "" +
+ "" + port.ToString() + "" +
+ "" + client.ToString() + "" +
+ "1" +
+ "" + description + "" +
+ "0" +
+ "",
+ "AddPortMapping");
+
+ m_peer.LogDebug("Sent UPnP port forward request");
+ System.Threading.Thread.Sleep(50);
+ }
+ catch (Exception ex)
+ {
+ m_peer.LogWarning("UPnP port forward failed: " + ex.Message);
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Delete a forwarding rule from the router using UPnP
+ ///
+ public bool DeleteForwardingRule(int port)
+ {
+ if (m_serviceUrl == null)
+ return false;
+ try
+ {
+ XmlDocument xdoc = SOAPRequest(m_serviceUrl,
+ "" +
+ "" +
+ "" +
+ "" + port + "" +
+ "" + ProtocolType.Udp.ToString().ToUpper() + "" +
+ "", "DeletePortMapping");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message);
+ return false;
+ }
+ }
+
+ ///
+ /// Retrieve the extern ip using UPnP
+ ///
+ public IPAddress GetExternalIP()
+ {
+ if (m_serviceUrl == null)
+ return null;
+
+ try
+ {
+ XmlDocument xdoc = SOAPRequest(m_serviceUrl, "" +
+ "", "GetExternalIPAddress");
+ XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
+ nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
+ string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value;
+ return IPAddress.Parse(IP);
+ }
+ catch (Exception ex)
+ {
+ m_peer.LogWarning("Failed to get external IP: " + ex.Message);
+ return null;
+ }
+ }
+
+ private XmlDocument SOAPRequest(string url, string soap, string function)
+ {
+ string req = "" +
+ "" +
+ "" +
+ soap +
+ "" +
+ "";
+ WebRequest r = HttpWebRequest.Create(url);
+ r.Method = "POST";
+ byte[] b = System.Text.Encoding.UTF8.GetBytes(req);
+ r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\"");
+ r.ContentType = "text/xml; charset=\"utf-8\"";
+ r.ContentLength = b.Length;
+ r.GetRequestStream().Write(b, 0, b.Length);
+ XmlDocument resp = new XmlDocument();
+ WebResponse wres = r.GetResponse();
+ Stream ress = wres.GetResponseStream();
+ resp.Load(ress);
+ return resp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/Program.cs b/UnitTests/Program.cs
index 93621ee..5070f88 100644
--- a/UnitTests/Program.cs
+++ b/UnitTests/Program.cs
@@ -1,6 +1,8 @@
using System;
using System.Reflection;
using Lidgren.Network;
+using System.Net;
+using System.Net.Sockets;
namespace UnitTests
{
@@ -9,11 +11,10 @@ namespace UnitTests
static void Main(string[] args)
{
NetPeerConfiguration config = new NetPeerConfiguration("unittests");
+ config.EnableUPnP = true;
NetPeer peer = new NetPeer(config);
peer.Start(); // needed for initialization
- System.Threading.Thread.Sleep(50);
-
Console.WriteLine("Unique identifier is " + NetUtility.ToHexString(peer.UniqueIdentifier));
ReadWriteTests.Run(peer);