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 0000000000..a9b77a3a8a Binary files /dev/null and b/thirdparty/Mono.Nat.dll differ