From aa36a56b27f7cde4ba8bbbbd60047483990b34f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Mon, 21 May 2012 17:06:03 +0200 Subject: [PATCH] UPnP source code fixes as suggested by Chris Forbes --- OpenRA.Game/GameRules/Settings.cs | 2 + OpenRA.Game/OpenRA.Game.csproj | 1 + OpenRA.Game/Server/Server.cs | 23 ++- OpenRA.Game/Server/UPnP.cs | 139 ++++++++++++++++++ .../Widgets/Logic/ServerCreationLogic.cs | 8 +- mods/ra/chrome/create-server.yaml | 6 + 6 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 OpenRA.Game/Server/UPnP.cs 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