fixed UPnP more crashes

proper timeout: does not crash/lag if no UPnP devices are found
close all ports, sockets, responses after they have been used
so it does not crash when port forwarding for the 2nd time
put all exceptions logs into OpenRA sourcecode
This commit is contained in:
Matthias Mailänder
2012-07-15 22:31:02 +02:00
parent 5e6b8deec1
commit 3728685c67
3 changed files with 132 additions and 68 deletions

View File

@@ -29,7 +29,7 @@ namespace OpenRA.GameRules
public int ExternalPort = 1234; public int ExternalPort = 1234;
public bool AdvertiseOnline = true; public bool AdvertiseOnline = true;
public string MasterServer = "http://master.open-ra.org/"; public string MasterServer = "http://master.open-ra.org/";
public bool AllowUPnP = true; public bool AllowUPnP = false;
public bool AllowCheats = false; public bool AllowCheats = false;
public string Map = null; public string Map = null;
public string[] Ban = null; public string[] Ban = null;

View File

@@ -52,7 +52,10 @@ namespace OpenRA.Server
XTimer gameTimeout; XTimer gameTimeout;
volatile bool shutdown = false; volatile bool shutdown = false;
public void Shutdown() { shutdown = true; } public void Shutdown()
{
shutdown = true;
}
public Server(IPEndPoint endpoint, string[] mods, ServerSettings settings, ModData modData) public Server(IPEndPoint endpoint, string[] mods, ServerSettings settings, ModData modData)
{ {
@@ -69,8 +72,43 @@ namespace OpenRA.Server
randomSeed = (int)DateTime.Now.ToBinary(); randomSeed = (int)DateTime.Now.ToBinary();
if (settings.AllowUPnP) if (Settings.AllowUPnP)
PortForward(); {
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;
}
}
foreach (var trait in modData.Manifest.ServerTraits) foreach (var trait in modData.Manifest.ServerTraits)
ServerTraits.Add( modData.ObjectCreator.CreateObject<ServerTrait>(trait) ); ServerTraits.Add( modData.ObjectCreator.CreateObject<ServerTrait>(trait) );
@@ -102,7 +140,11 @@ namespace OpenRA.Server
Socket.Select( checkRead, null, null, timeout ); Socket.Select( checkRead, null, null, timeout );
if (shutdown) if (shutdown)
{
if (Settings.AllowUPnP)
RemovePortforward();
break; break;
}
foreach( Socket s in checkRead ) foreach( Socket s in checkRead )
if( s == listener.Server ) AcceptConnection(); if( s == listener.Server ) AcceptConnection();
@@ -117,8 +159,12 @@ namespace OpenRA.Server
t.Tick(this); t.Tick(this);
if (shutdown) if (shutdown)
{
if (Settings.AllowUPnP)
RemovePortforward();
break; break;
} }
}
GameStarted = false; GameStarted = false;
foreach (var t in ServerTraits.WithInterface<INotifyServerShutdown>()) foreach (var t in ServerTraits.WithInterface<INotifyServerShutdown>())
@@ -129,20 +175,20 @@ namespace OpenRA.Server
try { listener.Stop(); } try { listener.Stop(); }
catch { } catch { }
} ) { IsBackground = true }.Start(); } ) { IsBackground = true }.Start();
} }
void PortForward() void RemovePortforward()
{ {
if (UPnP.NAT.Discover()) try
{ {
Log.Write("server", "UPnP-enabled router discovered."); if (UPnP.NAT.DeleteForwardingRule(Port, ProtocolType.Tcp))
UPnP.NAT.ForwardPort(Port, ProtocolType.Tcp, "OpenRA"); //might timeout after second try Log.Write("server", "Port {0} (TCP) forwarding rules has been removed.", Port);
Log.Write("server", "Port {0} (TCP) has been forwarded.", Port); }
Log.Write("server", "Your IP is: {0}", UPnP.NAT.GetExternalIP() ); catch (Exception e)
{
OpenRA.Log.Write("server", "Can not remove UPnP portforwarding rules: {0}", e);
} }
else
Log.Write("server", "No UPnP-enabled router detected.");
return;
} }
/* lobby rework todo: /* lobby rework todo:

View File

@@ -19,13 +19,13 @@ namespace UPnP
{ {
public class NAT public class NAT
{ {
public static TimeSpan _timeout = new TimeSpan(0, 0, 0, 3);
static string _serviceUrl; static string _serviceUrl;
public static bool Discover() public static bool Discover()
{ {
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
s.ReceiveTimeout = 3000; //3 seconds
string req = "M-SEARCH * HTTP/1.1\r\n" + string req = "M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" + "HOST: 239.255.255.250:1900\r\n" +
"ST:upnp:rootdevice\r\n" + "ST:upnp:rootdevice\r\n" +
@@ -35,14 +35,9 @@ namespace UPnP
IPEndPoint ipe = new IPEndPoint(IPAddress.Broadcast, 1900); IPEndPoint ipe = new IPEndPoint(IPAddress.Broadcast, 1900);
byte[] buffer = new byte[0x1000]; byte[] buffer = new byte[0x1000];
DateTime start = DateTime.Now;
try try
{
do
{ {
s.SendTo(data, ipe); s.SendTo(data, ipe);
int length = 0; int length = 0;
do do
{ {
@@ -54,23 +49,32 @@ namespace UPnP
resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9); resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
resp = resp.Substring(0, resp.IndexOf("\r")).Trim(); resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp))) if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp)))
{
s.Close();
return true; return true;
} }
}
} while (length > 0); } while (length > 0);
} while ((start - DateTime.Now) < _timeout); s.Close();
}
catch (Exception e)
{
OpenRA.Log.Write("server", "UPNP discover failed: {0}", e);
}
return false; return false;
} }
catch
{
s.Close();
return false;
}
}
private static String GetServiceUrl(string resp) private static String GetServiceUrl(string resp)
{ {
XmlDocument desc = new XmlDocument(); XmlDocument desc = new XmlDocument();
desc.Load(WebRequest.Create(resp).GetResponse().GetResponseStream()); 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); XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr); XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
@@ -83,8 +87,10 @@ namespace UPnP
Uri combinedUri = new Uri(respUri, node.Value); Uri combinedUri = new Uri(respUri, node.Value);
return combinedUri.AbsoluteUri; return combinedUri.AbsoluteUri;
} }
}
}
public static void ForwardPort(int port, ProtocolType protocol, string description) public static bool ForwardPort(int port, ProtocolType protocol, string description)
{ {
if (string.IsNullOrEmpty(_serviceUrl)) if (string.IsNullOrEmpty(_serviceUrl))
throw new Exception("No UPnP service available or Discover() has not been called"); throw new Exception("No UPnP service available or Discover() has not been called");
@@ -95,17 +101,23 @@ namespace UPnP
"<NewPortMappingDescription>{3}</NewPortMappingDescription>"+ "<NewPortMappingDescription>{3}</NewPortMappingDescription>"+
"<NewLeaseDuration>0</NewLeaseDuration></u:AddPortMapping>", "<NewLeaseDuration>0</NewLeaseDuration></u:AddPortMapping>",
port, protocol.ToString().ToUpper(),Dns.GetHostAddresses(Dns.GetHostName())[0], description); port, protocol.ToString().ToUpper(),Dns.GetHostAddresses(Dns.GetHostName())[0], description);
SOAPRequest(_serviceUrl, body, "AddPortMapping"); if (SOAPRequest(_serviceUrl, body, "AddPortMapping") != null)
return true;
else
return false;
} }
public static void DeleteForwardingRule(int port, ProtocolType protocol) public static bool DeleteForwardingRule(int port, ProtocolType protocol)
{ {
if (string.IsNullOrEmpty(_serviceUrl)) if (string.IsNullOrEmpty(_serviceUrl))
throw new Exception("No UPnP service available or Discover() has not been called"); 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\">" + string body = String.Format("<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>{0}</NewExternalPort>"+ "<NewRemoteHost></NewRemoteHost><NewExternalPort>{0}</NewExternalPort>"+
"<NewProtocol>{1}</NewProtocol></u:DeletePortMapping>", port, protocol.ToString().ToUpper() ); "<NewProtocol>{1}</NewProtocol></u:DeletePortMapping>", port, protocol.ToString().ToUpper() );
SOAPRequest(_serviceUrl, body, "DeletePortMapping"); if (SOAPRequest(_serviceUrl, body, "DeletePortMapping") != null)
return true;
else
return false;
} }
public static IPAddress GetExternalIP() public static IPAddress GetExternalIP()
@@ -122,24 +134,30 @@ namespace UPnP
private static XmlDocument SOAPRequest(string url, string soap, string function) private static XmlDocument SOAPRequest(string url, string soap, string function)
{ {
string req = "<?xml version=\"1.0\"?>" + 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:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
"<s:Body>" + "<s:Body>" +
soap + soap +
"</s:Body>" + "</s:Body>" +
"</s:Envelope>"; "</s:Envelope>";
WebRequest r = HttpWebRequest.Create(url); HttpWebRequest r = (HttpWebRequest)WebRequest.Create(url);
r.KeepAlive = false;
r.Method = "POST"; r.Method = "POST";
byte[] b = Encoding.UTF8.GetBytes(req); byte[] b = Encoding.UTF8.GetBytes(body);
r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\""); r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\"");
r.ContentType = "text/xml; charset=\"utf-8\""; r.ContentType = "text/xml; charset=\"utf-8\"";
r.ContentLength = b.Length; r.ContentLength = b.Length;
r.GetRequestStream().Write(b, 0, b.Length); Stream newStream = r.GetRequestStream();
newStream.Write(b, 0, b.Length);
XmlDocument resp = new XmlDocument(); XmlDocument resp = new XmlDocument();
WebResponse wres = r.GetResponse(); using (WebResponse wres = r.GetResponse())
Stream ress = wres.GetResponseStream(); {
using (Stream ress = wres.GetResponseStream())
{
resp.Load(ress); resp.Load(ress);
return resp; return resp;
} }
} }
} }
}
}