diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs
index 49fa8918f4..f1265e5e40 100644
--- a/OpenRA.Game/GameRules/Settings.cs
+++ b/OpenRA.Game/GameRules/Settings.cs
@@ -29,6 +29,7 @@ namespace OpenRA.GameRules
public int ExternalPort = 1234;
public bool AdvertiseOnline = true;
public string MasterServer = "http://master.open-ra.org/";
+ public bool AllowUPnP = true;
public bool AllowCheats = false;
public string Map = null;
public string[] Ban = null;
@@ -42,6 +43,7 @@ namespace OpenRA.GameRules
ExternalPort = other.ExternalPort;
AdvertiseOnline = other.AdvertiseOnline;
MasterServer = other.MasterServer;
+ AllowUPnP = other.AllowUPnP;
AllowCheats = other.AllowCheats;
Map = other.Map;
Ban = other.Ban;
diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj
index ea15d2ac38..17ab5219b7 100755
--- a/OpenRA.Game/OpenRA.Game.csproj
+++ b/OpenRA.Game/OpenRA.Game.csproj
@@ -142,6 +142,7 @@
+
diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs
index 6af0357e56..c443b8c94a 100644
--- a/OpenRA.Game/Server/Server.cs
+++ b/OpenRA.Game/Server/Server.cs
@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
- * Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
+ * Copyright 2007-2012 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
@@ -13,9 +13,13 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text;
using System.Net;
using System.Net.Sockets;
+using System.Net.NetworkInformation;
+using UPnP;
using System.Threading;
+using System.Xml;
using OpenRA.FileFormats;
using OpenRA.GameRules;
using OpenRA.Network;
@@ -64,6 +68,9 @@ namespace OpenRA.Server
randomSeed = (int)DateTime.Now.ToBinary();
+ if (settings.AllowUPnP)
+ PortForward();
+
foreach (var trait in modData.Manifest.ServerTraits)
ServerTraits.Add( modData.ObjectCreator.CreateObject(trait) );
@@ -123,6 +130,20 @@ namespace OpenRA.Server
} ) { IsBackground = true }.Start();
}
+ void PortForward()
+ {
+ if (UPnP.NAT.Discover())
+ {
+ Log.Write("server", "UPnP-enabled router discovered.");
+ UPnP.NAT.ForwardPort(Port, ProtocolType.Tcp, "OpenRA"); //might timeout after second try
+ Log.Write("server", "Port {0} (TCP) has been forwarded.", Port);
+ Log.Write("server", "Your IP is: {0}", UPnP.NAT.GetExternalIP() );
+ }
+ else
+ Log.Write("server", "No UPnP-enabled router detected.");
+ return;
+ }
+
/* lobby rework todo:
* - "teams together" option for team games -- will eliminate most need
* for manual spawnpoint choosing.
diff --git a/OpenRA.Game/Server/UPnP.cs b/OpenRA.Game/Server/UPnP.cs
new file mode 100644
index 0000000000..a64b26126e
--- /dev/null
+++ b/OpenRA.Game/Server/UPnP.cs
@@ -0,0 +1,139 @@
+#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
+ {
+ public static TimeSpan _timeout = new TimeSpan(0, 0, 0, 3);
+ static string _serviceUrl;
+
+ public static bool Discover()
+ {
+ Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
+ 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];
+
+ DateTime start = DateTime.Now;
+
+ do
+ {
+ s.SendTo(data, ipe);
+ s.SendTo(data, ipe);
+ 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)))
+ return true;
+ }
+ } while (length > 0);
+ } while ((start - DateTime.Now) < _timeout);
+ return false;
+ }
+
+ private static String GetServiceUrl(string resp)
+ {
+ 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 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 void 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);
+ SOAPRequest(_serviceUrl, body, "AddPortMapping");
+ }
+
+ public static void 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() );
+ SOAPRequest(_serviceUrl, body, "DeletePortMapping");
+ }
+
+ 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 req = "" +
+ "" +
+ "" +
+ soap +
+ "" +
+ "";
+ WebRequest r = HttpWebRequest.Create(url);
+ r.Method = "POST";
+ byte[] b = 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;
+ }
+ }
+}
diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs
index abe2a490e7..e93d00a2f4 100644
--- a/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs
+++ b/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs
@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
- * Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
+ * Copyright 2007-2012 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
@@ -22,6 +22,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
Action onExit;
Map map;
bool advertiseOnline;
+ bool allowUPnP;
[ObjectCreator.UseCtor]
public ServerCreationLogic(Widget widget, Action onExit, Action openLobby)
@@ -64,6 +65,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var advertiseCheckbox = panel.Get("ADVERTISE_CHECKBOX");
advertiseCheckbox.IsChecked = () => advertiseOnline;
advertiseCheckbox.OnClick = () => advertiseOnline ^= true;
+
+ var UPnPCheckbox = panel.Get("UPNP_CHECKBOX");
+ UPnPCheckbox.IsChecked = () => allowUPnP;
+ UPnPCheckbox.OnClick = () => allowUPnP ^= true;
}
void CreateAndJoin()
@@ -81,6 +86,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
Game.Settings.Server.ListenPort = listenPort;
Game.Settings.Server.ExternalPort = externalPort;
Game.Settings.Server.AdvertiseOnline = advertiseOnline;
+ Game.Settings.Server.AllowUPnP = allowUPnP;
Game.Settings.Server.Map = map.Uid;
Game.Settings.Save();
diff --git a/mods/ra/chrome/create-server.yaml b/mods/ra/chrome/create-server.yaml
index 2fd4466391..3a81044bd2 100644
--- a/mods/ra/chrome/create-server.yaml
+++ b/mods/ra/chrome/create-server.yaml
@@ -60,6 +60,12 @@ Background@CREATESERVER_BG:
Width:300
Height:20
Text:Advertise game Online
+ Checkbox@UPNP_CHECKBOX:
+ X:165
+ Y:165
+ Width:300
+ Height:20
+ Text:Allow UPnP port forwarding
Button@CREATE_BUTTON:
X:130
Y:PARENT_BOTTOM - 45