use Mono.Nat for UPnP port forwarding

- might support more devices
- supports internal and external port mapping
- discover the device only once (at startup)
This commit is contained in:
Matthias Mailänder
2013-04-05 14:18:15 +02:00
parent 8ab817f466
commit 989c23e632
9 changed files with 95 additions and 213 deletions

View File

@@ -16,14 +16,18 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using UPnP;
using System.Threading;
using OpenRA.FileFormats;
using OpenRA.GameRules;
using OpenRA.Network;
using XTimer = System.Timers.Timer;
using Mono.Nat;
using Mono.Nat.Pmp;
using Mono.Nat.Upnp;
namespace OpenRA.Server
{
public enum ServerState : int
@@ -50,6 +54,8 @@ namespace OpenRA.Server
public readonly IPAddress Ip;
public readonly int Port;
public INatDevice NatDevice;
int randomSeed;
public readonly Thirdparty.Random Random = new Thirdparty.Random();
@@ -78,7 +84,7 @@ namespace OpenRA.Server
RemovePortforward();
}
public Server(IPEndPoint endpoint, string[] mods, ServerSettings settings, ModData modData)
public Server(IPEndPoint endpoint, string[] mods, ServerSettings settings, ModData modData, INatDevice natDevice)
{
Log.AddChannel("server", "server.log");
@@ -91,46 +97,12 @@ namespace OpenRA.Server
Settings = settings;
ModData = modData;
NatDevice = natDevice;
randomSeed = (int)DateTime.Now.ToBinary();
if (Settings.AllowUPnP)
{
try
{
if (UPnP.NAT.Discover())
{
Log.Write("server", "UPnP-enabled router discovered.");
Log.Write("server", "Your IP is: {0}", UPnP.NAT.GetExternalIP() );
}
else
{
Log.Write("server", "No UPnP-enabled router detected.");
Settings.AllowUPnP = false;
}
}
catch (Exception e)
{
OpenRA.Log.Write("server", "Can't discover UPnP-enabled routers: {0}", e);
Settings.AllowUPnP = false;
}
}
if (Settings.AllowUPnP)
{
try
{
if (UPnP.NAT.ForwardPort(Port, ProtocolType.Tcp, "OpenRA"))
Log.Write("server", "Port {0} (TCP) has been forwarded.", Port);
else
Settings.AllowUPnP = false;
}
catch (Exception e)
{
OpenRA.Log.Write("server", "Can not forward ports via UPnP: {0}", e);
Settings.AllowUPnP = false;
}
}
ForwardPort();
foreach (var trait in modData.Manifest.ServerTraits)
ServerTraits.Add( modData.ObjectCreator.CreateObject<ServerTrait>(trait) );
@@ -194,6 +166,9 @@ namespace OpenRA.Server
foreach (var t in ServerTraits.WithInterface<INotifyServerShutdown>())
t.ServerShutdown(this);
if (Settings.AllowUPnP)
RemovePortforward();
preConns.Clear();
conns.Clear();
try { listener.Stop(); }
@@ -202,16 +177,33 @@ namespace OpenRA.Server
}
void ForwardPort()
{
try
{
Mapping mapping = new Mapping (Protocol.Tcp, Settings.ExternalPort, Settings.ListenPort);
NatDevice.CreatePortMap(mapping);
Log.Write("server", "Create port mapping: protocol={0}, public={1}, private={2}", mapping.Protocol, mapping.PublicPort, mapping.PrivatePort);
}
catch (Exception e)
{
OpenRA.Log.Write("server", "Can not forward ports via UPnP: {0}", e);
Settings.AllowUPnP = false;
}
}
void RemovePortforward()
{
try
{
if (UPnP.NAT.DeleteForwardingRule(Port, ProtocolType.Tcp))
Log.Write("server", "Port {0} (TCP) forwarding rules has been removed.", Port);
Mapping mapping = new Mapping (Protocol.Tcp, Settings.ExternalPort, Settings.ListenPort);
NatDevice.DeletePortMap (mapping);
Log.Write("server", "Remove port mapping: protocol={0}, public={1}, private={2}", mapping.Protocol, mapping.PublicPort, mapping.PrivatePort);
}
catch (Exception e)
catch (Exception e)
{
OpenRA.Log.Write("server", "Can not remove UPnP portforwarding rules: {0}", e);
Settings.AllowUPnP = false;
}
}

View File

@@ -1,163 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2008-2009 http://www.codeproject.com/Members/Harold-Aptroot
* Source: http://www.codeproject.com/Articles/27992/NAT-Traversal-with-UPnP-in-C
* This file is licensed under A Public Domain dedication.
* For more information, see http://creativecommons.org/licenses/publicdomain/
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Xml;
using System.IO;
namespace UPnP
{
public class NAT
{
static string _serviceUrl;
public static bool Discover()
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
s.ReceiveTimeout = 3000; //3 seconds
string req = "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[] data = Encoding.ASCII.GetBytes(req);
IPEndPoint ipe = new IPEndPoint(IPAddress.Broadcast, 1900);
byte[] buffer = new byte[0x1000];
try
{
s.SendTo(data, ipe);
int length = 0;
do
{
length = s.Receive(buffer);
string resp = Encoding.ASCII.GetString(buffer, 0, length).ToLower();
if (resp.Contains("upnp:rootdevice"))
{
resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp)))
{
s.Close();
return true;
}
}
} while (length > 0);
s.Close();
return false;
}
catch
{
s.Close();
return false;
}
}
private static String GetServiceUrl(string resp)
{
XmlDocument desc = new XmlDocument();
HttpWebRequest r = (HttpWebRequest)WebRequest.Create(resp);
r.KeepAlive = false;
using (WebResponse wres = r.GetResponse())
{
using (Stream ress = wres.GetResponseStream())
{
desc.Load(ress);
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 null;
XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:WANIPConnection:1\"]/tns:controlURL/text()", nsMgr);
if (node == null)
return null;
Uri respUri = new Uri(resp);
Uri combinedUri = new Uri(respUri, node.Value);
return combinedUri.AbsoluteUri;
}
}
}
public static bool ForwardPort(int port, ProtocolType protocol, string description)
{
if (string.IsNullOrEmpty(_serviceUrl))
throw new Exception("No UPnP service available or Discover() has not been called");
string body = String.Format("<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"+
"<NewRemoteHost></NewRemoteHost><NewExternalPort>{0}</NewExternalPort>"+
"<NewProtocol>{1}</NewProtocol><NewInternalPort>{0}</NewInternalPort>" +
"<NewInternalClient>{2}</NewInternalClient><NewEnabled>1</NewEnabled>" +
"<NewPortMappingDescription>{3}</NewPortMappingDescription>"+
"<NewLeaseDuration>0</NewLeaseDuration></u:AddPortMapping>",
port, protocol.ToString().ToUpper(),Dns.GetHostAddresses(Dns.GetHostName())[0], description);
if (SOAPRequest(_serviceUrl, body, "AddPortMapping") != null)
return true;
else
return false;
}
public static bool DeleteForwardingRule(int port, ProtocolType protocol)
{
if (string.IsNullOrEmpty(_serviceUrl))
throw new Exception("No UPnP service available or Discover() has not been called");
string body = String.Format("<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>{0}</NewExternalPort>"+
"<NewProtocol>{1}</NewProtocol></u:DeletePortMapping>", port, protocol.ToString().ToUpper() );
if (SOAPRequest(_serviceUrl, body, "DeletePortMapping") != null)
return true;
else
return false;
}
public static IPAddress GetExternalIP()
{
if (string.IsNullOrEmpty(_serviceUrl))
throw new Exception("No UPnP service available or Discover() has not been called");
XmlDocument xdoc = SOAPRequest(_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);
}
private static XmlDocument SOAPRequest(string url, string soap, string function)
{
string body = "<?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>";
HttpWebRequest r = (HttpWebRequest)WebRequest.Create(url);
r.KeepAlive = false;
r.Method = "POST";
byte[] b = Encoding.UTF8.GetBytes(body);
r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\"");
r.ContentType = "text/xml; charset=\"utf-8\"";
r.ContentLength = b.Length;
Stream newStream = r.GetRequestStream();
newStream.Write(b, 0, b.Length);
XmlDocument resp = new XmlDocument();
using (WebResponse wres = r.GetResponse())
{
using (Stream ress = wres.GetResponseStream())
{
resp.Load(ress);
return resp;
}
}
}
}
}