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:
3
Makefile
3
Makefile
@@ -1,7 +1,7 @@
|
||||
CSC = gmcs
|
||||
CSFLAGS = -nologo -warn:4 -debug:full -optimize- -codepage:utf8 -unsafe -warnaserror
|
||||
DEFINE = DEBUG;TRACE
|
||||
COMMON_LIBS = System.dll System.Core.dll System.Drawing.dll System.Xml.dll thirdparty/ICSharpCode.SharpZipLib.dll thirdparty/FuzzyLogicLibrary.dll
|
||||
COMMON_LIBS = System.dll System.Core.dll System.Drawing.dll System.Xml.dll thirdparty/ICSharpCode.SharpZipLib.dll thirdparty/FuzzyLogicLibrary.dll thirdparty/Mono.Nat.dll
|
||||
PHONY = core tools package all mods clean distclean dependencies
|
||||
|
||||
.SUFFIXES:
|
||||
@@ -258,6 +258,7 @@ install: all
|
||||
@$(INSTALL_PROGRAM) thirdparty/FuzzyLogicLibrary.dll $(INSTALL_DIR)
|
||||
@$(INSTALL_PROGRAM) thirdparty/SharpFont.dll $(INSTALL_DIR)
|
||||
@cp thirdparty/SharpFont.dll.config $(INSTALL_DIR)
|
||||
@$(INSTALL_PROGRAM) thirdparty/Mono.Nat.dll $(INSTALL_DIR)
|
||||
|
||||
@echo "#!/bin/sh" > openra
|
||||
@echo 'BINDIR=$$(dirname $$(readlink -f $$0))' >> openra
|
||||
|
||||
@@ -21,6 +21,10 @@ using OpenRA.Network;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
using Mono.Nat;
|
||||
using Mono.Nat.Pmp;
|
||||
using Mono.Nat.Upnp;
|
||||
|
||||
using XRandom = OpenRA.Thirdparty.Random;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -34,6 +38,8 @@ namespace OpenRA
|
||||
public static ModData modData;
|
||||
static WorldRenderer worldRenderer;
|
||||
|
||||
public static INatDevice natDevice;
|
||||
|
||||
public static Viewport viewport;
|
||||
public static Settings Settings;
|
||||
|
||||
@@ -256,6 +262,18 @@ namespace OpenRA
|
||||
Log.AddChannel("perf", "perf.log");
|
||||
Log.AddChannel("debug", "debug.log");
|
||||
Log.AddChannel("sync", "syncreport.log");
|
||||
Log.AddChannel("server", "server.log");
|
||||
|
||||
try {
|
||||
NatUtility.DeviceFound += DeviceFound;
|
||||
NatUtility.DeviceLost += DeviceLost;
|
||||
|
||||
NatUtility.StartDiscovery();
|
||||
OpenRA.Log.Write("server", "NAT discovery started.");
|
||||
} catch (Exception e) {
|
||||
OpenRA.Log.Write("server", "Can't discover UPnP-enabled device: {0}", e);
|
||||
Settings.Server.AllowUPnP = false;
|
||||
}
|
||||
|
||||
FileSystem.Mount("."); // Needed to access shaders
|
||||
Renderer.Initialize( Game.Settings.Graphics.Mode );
|
||||
@@ -269,6 +287,31 @@ namespace OpenRA
|
||||
InitializeWithMods(Settings.Game.Mods);
|
||||
}
|
||||
|
||||
public static void DeviceFound (object sender, DeviceEventArgs args)
|
||||
{
|
||||
natDevice = args.Device;
|
||||
|
||||
Log.Write ("server", "NAT device discovered.");
|
||||
Log.Write ("server", "Type: {0}", natDevice.GetType ().Name);
|
||||
Log.Write ("server", "Your external IP is: {0}", natDevice.GetExternalIP ());
|
||||
|
||||
foreach (Mapping mp in natDevice.GetAllMappings()) {
|
||||
Log.Write ("server", "Existing port mapping: protocol={0}, public={1}, private={2}", mp.Protocol, mp.PublicPort, mp.PrivatePort);
|
||||
}
|
||||
|
||||
Settings.Server.AllowUPnP = true;
|
||||
}
|
||||
|
||||
public static void DeviceLost (object sender, DeviceEventArgs args)
|
||||
{
|
||||
natDevice = args.Device;
|
||||
|
||||
Log.Write("server", "NAT device Lost");
|
||||
Log.Write("server", "Type: {0}", natDevice.GetType().Name);
|
||||
|
||||
Settings.Server.AllowUPnP = false;
|
||||
}
|
||||
|
||||
public static void InitializeWithMods(string[] mods)
|
||||
{
|
||||
// Clear static state if we have switched mods
|
||||
@@ -415,7 +458,7 @@ namespace OpenRA
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
{
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort),
|
||||
Game.Settings.Game.Mods, settings, modData);
|
||||
Game.Settings.Game.Mods, settings, modData, natDevice);
|
||||
}
|
||||
|
||||
public static int CreateLocalServer(string map)
|
||||
@@ -432,7 +475,7 @@ namespace OpenRA
|
||||
settings.AllowUPnP = false;
|
||||
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0),
|
||||
Game.Settings.Game.Mods, settings, modData);
|
||||
Game.Settings.Game.Mods, settings, modData, natDevice);
|
||||
|
||||
return server.Port;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace OpenRA.GameRules
|
||||
public int ExternalPort = 1234;
|
||||
public bool AdvertiseOnline = true;
|
||||
public string MasterServer = "http://master.open-ra.org/";
|
||||
public bool AllowUPnP = false;
|
||||
public bool AllowUPnP = true;
|
||||
public bool AllowCheats = false;
|
||||
public string Map = null;
|
||||
public string[] Ban = null;
|
||||
|
||||
@@ -60,8 +60,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
@@ -69,6 +68,12 @@
|
||||
<Reference Include="SharpFont">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\thirdparty\SharpFont.dll</HintPath>
|
||||
<Reference Include="Mono.Nat, Version=1.1.0.0, Culture=neutral">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<Private>False</Private>
|
||||
<Package>mono.nat</Package>
|
||||
<HintPath>..\thirdparty\Mono.Nat.dll</HintPath>
|
||||
</Reference>
|
||||
</Reference>
|
||||
<Reference Include="Tao.OpenAl, Version=1.1.0.1, Culture=neutral, PublicKeyToken=a7579dda88828311">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
@@ -151,7 +156,6 @@
|
||||
<Compile Include="Server\Server.cs" />
|
||||
<Compile Include="Server\ServerOrder.cs" />
|
||||
<Compile Include="Server\TraitInterfaces.cs" />
|
||||
<Compile Include="Server\UPnP.cs" />
|
||||
<Compile Include="Sound.cs" />
|
||||
<Compile Include="Support\Arguments.cs" />
|
||||
<Compile Include="Support\PerfHistory.cs" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,9 @@ cp thirdparty/FuzzyLogicLibrary.dll packaging/built
|
||||
# SharpFont for FreeType support
|
||||
cp thirdparty/SharpFont* packaging/built
|
||||
|
||||
# Mono.NAT for UPnP support
|
||||
cp thirdparty/Mono.Nat.dll packaging/built
|
||||
|
||||
# Copy game icon for windows package
|
||||
cp OpenRA.Game/OpenRA.ico packaging/built
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ Section "Client" Client
|
||||
File "${SRCDIR}\OpenRA.Renderer.Null.dll"
|
||||
File "${SRCDIR}\ICSharpCode.SharpZipLib.dll"
|
||||
File "${SRCDIR}\FuzzyLogicLibrary.dll"
|
||||
File "${SRCDIR}\Mono.Nat.dll"
|
||||
File "${SRCDIR}\COPYING"
|
||||
File "${SRCDIR}\HACKING"
|
||||
File "${SRCDIR}\INSTALL"
|
||||
@@ -263,6 +264,7 @@ Function ${UN}Clean
|
||||
Delete $INSTDIR\OpenRA.Renderer.SdlCommon.dll
|
||||
Delete $INSTDIR\ICSharpCode.SharpZipLib.dll
|
||||
Delete $INSTDIR\FuzzyLogicLibrary.dll
|
||||
Delete $INSTDIR\Mono.Nat.dll
|
||||
Delete $INSTDIR\Tao.*.dll
|
||||
Delete $INSTDIR\SharpFont.*.dll
|
||||
Delete $INSTDIR\COPYING
|
||||
|
||||
BIN
thirdparty/Mono.Nat.dll
vendored
Normal file
BIN
thirdparty/Mono.Nat.dll
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user