1
0
mirror of https://github.com/lidgren/lidgren-network-gen3.git synced 2026-05-15 14:46:29 +09:00

UPnP support added: get external ip, add forwarding rule and delete forwarding rule

This commit is contained in:
lidgren
2011-04-27 20:52:29 +00:00
parent b575f72186
commit 713d91ac58
7 changed files with 353 additions and 112 deletions

View File

@@ -118,6 +118,7 @@
<Compile Include="NetUnreliableSenderChannel.cs" />
<Compile Include="NetUnreliableSequencedReceiver.cs" />
<Compile Include="NetUnreliableUnorderedReceiver.cs" />
<Compile Include="NetUPnP.cs" />
<Compile Include="NetUtility.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SenderChannelBase.cs" />

View File

@@ -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<NetIncomingMessage> 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)

View File

@@ -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);

View File

@@ -43,6 +43,11 @@ namespace Lidgren.Network
/// </summary>
public int Port { get { return m_listenPort; } }
/// <summary>
/// Returns an UPnP object if enabled in the NetPeerConfiguration
/// </summary>
public NetUPnP UPnP { get { return m_upnp; } }
/// <summary>
/// Gets or sets the application defined object containing data about the peer
/// </summary>
@@ -99,7 +104,7 @@ namespace Lidgren.Network
m_handshakes = new Dictionary<IPEndPoint, NetConnection>();
m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
m_status = NetPeerStatus.NotRunning;
m_receivedFragmentGroups = new Dictionary<NetConnection, Dictionary<int, ReceivedFragmentGroup>>();
m_receivedFragmentGroups = new Dictionary<NetConnection, Dictionary<int, ReceivedFragmentGroup>>();
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -258,18 +267,20 @@ namespace Lidgren.Network
}
}
#if DEBUG
/// <summary>
/// Send raw bytes; only used for debugging
/// </summary>
#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
/// <summary>
/// Disconnects all active connections and closes the socket

View File

@@ -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
}
}
/// <summary>
/// Enables UPnP support; enabling port forwarding and getting external ip
/// </summary>
public bool EnableUPnP
{
get { return m_enableUPnP; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_enableUPnP = value;
}
}
/// <summary>
/// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized.
/// </summary>

186
Lidgren.Network/NetUPnP.cs Normal file
View File

@@ -0,0 +1,186 @@
using System;
using System.IO;
using System.Xml;
using System.Net;
using System.Net.Sockets;
namespace Lidgren.Network
{
/// <summary>
/// UPnP support class
/// </summary>
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;
}
/// <summary>
/// Add a forwarding rule to the router using UPnP
/// </summary>
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,
"<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + port.ToString() + "</NewExternalPort>" +
"<NewProtocol>" + ProtocolType.Udp.ToString().ToUpper() + "</NewProtocol>" +
"<NewInternalPort>" + port.ToString() + "</NewInternalPort>" +
"<NewInternalClient>" + client.ToString() + "</NewInternalClient>" +
"<NewEnabled>1</NewEnabled>" +
"<NewPortMappingDescription>" + description + "</NewPortMappingDescription>" +
"<NewLeaseDuration>0</NewLeaseDuration>" +
"</u:AddPortMapping>",
"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;
}
/// <summary>
/// Delete a forwarding rule from the router using UPnP
/// </summary>
public bool DeleteForwardingRule(int port)
{
if (m_serviceUrl == null)
return false;
try
{
XmlDocument xdoc = SOAPRequest(m_serviceUrl,
"<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">" +
"<NewRemoteHost>" +
"</NewRemoteHost>" +
"<NewExternalPort>" + port + "</NewExternalPort>" +
"<NewProtocol>" + ProtocolType.Udp.ToString().ToUpper() + "</NewProtocol>" +
"</u:DeletePortMapping>", "DeletePortMapping");
return true;
}
catch (Exception ex)
{
m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message);
return false;
}
}
/// <summary>
/// Retrieve the extern ip using UPnP
/// </summary>
public IPAddress GetExternalIP()
{
if (m_serviceUrl == null)
return null;
try
{
XmlDocument xdoc = SOAPRequest(m_serviceUrl, "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">" +
"</u:GetExternalIPAddress>", "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 = "<?xml version=\"1.0\"?>" +
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
"<s:Body>" +
soap +
"</s:Body>" +
"</s:Envelope>";
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;
}
}
}

View File

@@ -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);