From 989c23e632410d4b37fd9d549efb683782ecdf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Fri, 5 Apr 2013 14:18:15 +0200 Subject: [PATCH] use Mono.Nat for UPnP port forwarding - might support more devices - supports internal and external port mapping - discover the device only once (at startup) --- Makefile | 3 +- OpenRA.Game/Game.cs | 47 ++++++++- OpenRA.Game/GameRules/Settings.cs | 2 +- OpenRA.Game/OpenRA.Game.csproj | 10 +- OpenRA.Game/Server/Server.cs | 74 ++++++-------- OpenRA.Game/Server/UPnP.cs | 163 ------------------------------ packaging/package-all.sh | 3 + packaging/windows/OpenRA.nsi | 6 +- thirdparty/Mono.Nat.dll | Bin 0 -> 43008 bytes 9 files changed, 95 insertions(+), 213 deletions(-) delete mode 100644 OpenRA.Game/Server/UPnP.cs create mode 100644 thirdparty/Mono.Nat.dll diff --git a/Makefile b/Makefile index c0d79ea085..ac3d4a1116 100644 --- a/Makefile +++ b/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 diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index d570f0e5ed..4b1734bc15 100755 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -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; } diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index 64ca77cb25..527fe66866 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -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; diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 6f7b5cbd0c..5053d7c492 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -60,8 +60,7 @@ - - + @@ -69,6 +68,12 @@ False ..\thirdparty\SharpFont.dll + + False + False + mono.nat + ..\thirdparty\Mono.Nat.dll + False @@ -151,7 +156,6 @@ - diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 33086d414a..79236160a1 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.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(trait) ); @@ -194,6 +166,9 @@ namespace OpenRA.Server foreach (var t in ServerTraits.WithInterface()) 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; } } diff --git a/OpenRA.Game/Server/UPnP.cs b/OpenRA.Game/Server/UPnP.cs deleted file mode 100644 index a4c7a0951e..0000000000 --- a/OpenRA.Game/Server/UPnP.cs +++ /dev/null @@ -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(""+ - "{0}"+ - "{1}{0}" + - "{2}1" + - "{3}"+ - "0", - 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("" + - "{0}"+ - "{1}", 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, "" + - "", "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 = "" + - "" + - "" + - soap + - "" + - ""; - 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; - } - } - } - } -} diff --git a/packaging/package-all.sh b/packaging/package-all.sh index b3c48d64ad..91961f0418 100755 --- a/packaging/package-all.sh +++ b/packaging/package-all.sh @@ -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 diff --git a/packaging/windows/OpenRA.nsi b/packaging/windows/OpenRA.nsi index bc1d82df90..4275cbd933 100644 --- a/packaging/windows/OpenRA.nsi +++ b/packaging/windows/OpenRA.nsi @@ -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" @@ -91,13 +92,13 @@ Section "Client" Client File "${SRCDIR}\OpenRA.ico" File "${SRCDIR}\Tao.*.dll" File "${SRCDIR}\SharpFont.*.dll" - + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application CreateDirectory "$SMPROGRAMS\$StartMenuFolder" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\OpenRA.lnk" $OUTDIR\OpenRA.Game.exe "" \ "$OUTDIR\OpenRA.Game.exe" "" "" "" "" !insertmacro MUI_STARTMENU_WRITE_END - + SetOutPath "$INSTDIR\cg" File "${SRCDIR}\cg\*.fx" SetOutPath "$INSTDIR\glsl" @@ -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 diff --git a/thirdparty/Mono.Nat.dll b/thirdparty/Mono.Nat.dll new file mode 100644 index 0000000000000000000000000000000000000000..a9b77a3a8a732384858f9660b7282377e6f7d95d GIT binary patch literal 43008 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 J|DV&q{{qZDd=>xz literal 0 HcmV?d00001