From e7a04096452fd9c99a43c840d741fe17532df5e6 Mon Sep 17 00:00:00 2001 From: 20kdc Date: Sun, 21 Nov 2021 14:19:39 +0000 Subject: [PATCH] Status host uses net.port by default, add UPnP port forwarding option (#2237) Co-authored-by: Pieter-Jan Briers --- Lidgren.Network/Lidgren.Network | 2 +- Robust.Server/ServerStatus/StatusHost.cs | 3 + Robust.Server/server_config.toml | 22 ++++++-- Robust.Shared/CVars.cs | 10 +++- Robust.Shared/Network/NetManager.Upnp.cs | 70 ++++++++++++++++++++++++ Robust.Shared/Network/NetManager.cs | 6 ++ 6 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 Robust.Shared/Network/NetManager.Upnp.cs diff --git a/Lidgren.Network/Lidgren.Network b/Lidgren.Network/Lidgren.Network index b5483c7a7..1dd5c1f33 160000 --- a/Lidgren.Network/Lidgren.Network +++ b/Lidgren.Network/Lidgren.Network @@ -1 +1 @@ -Subproject commit b5483c7a7e56039796391c98af8495e6f4d36528 +Subproject commit 1dd5c1f3339a693e440afef6f8445880e5a8c63c diff --git a/Robust.Server/ServerStatus/StatusHost.cs b/Robust.Server/ServerStatus/StatusHost.cs index d373a1a1b..8d8bb479f 100644 --- a/Robust.Server/ServerStatus/StatusHost.cs +++ b/Robust.Server/ServerStatus/StatusHost.cs @@ -156,6 +156,9 @@ namespace Robust.Server.ServerStatus private void RegisterCVars() { + // Set status host binding to match network manager by default + SetCVarIfUnmodified(CVars.StatusBind, $"*:{_netManager.Port}"); + // Check build.json var path = PathHelpers.ExecutableRelativeFile("build.json"); if (File.Exists(path)) diff --git a/Robust.Server/server_config.toml b/Robust.Server/server_config.toml index 7446e9468..45695ccd6 100644 --- a/Robust.Server/server_config.toml +++ b/Robust.Server/server_config.toml @@ -1,4 +1,7 @@ - +# Welcome to the example configuration file! +# Remember that if this is in bin/Content.Server or such, it may be overwritten on build. +# Consider copying it and using the --config-file and --data-dir options. + [log] path = "logs" format = "log_%(date)s-%(time)s.txt" @@ -9,19 +12,27 @@ enabled = false tickrate = 60 port = 1212 bindto = "::,0.0.0.0" +# Automatic port forwarding! +# Disabled by default because you may not want to do this. +# upnp = true -# The status server is the TCP side, used by the launcher to determine engine version, etc. [status] +# The status server is the TCP side, used by the launcher to determine engine version, etc. +# To be clear: Disabling it makes the launcher unable to connect! enabled = true -bind = "*:1212" + +# This is the address and port the status server binds to. +# The port is by default set based on net.port so it will follow what you set there. +# bind = "*:1212" # This is the address of the SS14 server as the launcher uses it. -# This is only needed if you're proxying the status HTTP server. +# This is only needed if you're proxying the status HTTP server - +# by default the launcher will assume the address and port match that of the status server. # connectaddress = "udp://localhost:1212" [game] hostname = "MyServer" -# map = "maps/saltern.yml" +# map = "Maps/saltern.yml" maxplayers = 64 type = 1 welcomemsg = "Welcome to the server!" @@ -62,6 +73,7 @@ loginlocal = true # Build hash - this is a *capitalized* SHA256 hash of the client ZIP. # Optional in any case and automatically set if hosting a client ZIP. +# This hash is an example only. # build = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" [auth] diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 07cebe14d..22fc41eae 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -113,6 +113,12 @@ namespace Robust.Shared public static readonly CVarDef NetEncrypt = CVarDef.Create("net.encrypt", true, CVar.CLIENTONLY); + /// + /// If true, use UPnP to automatically forward ports on startup if possible. + /// + public static readonly CVarDef NetUPnP = + CVarDef.Create("net.upnp", false, CVar.SERVERONLY); + /** * SUS */ @@ -163,8 +169,10 @@ namespace Robust.Shared public static readonly CVarDef StatusEnabled = CVarDef.Create("status.enabled", true, CVar.ARCHIVE | CVar.SERVERONLY); + // Example: *:1212 + // But this is now autogenerated by default to match NetPort public static readonly CVarDef StatusBind = - CVarDef.Create("status.bind", "*:1212", CVar.ARCHIVE | CVar.SERVERONLY); + CVarDef.Create("status.bind", "", CVar.ARCHIVE | CVar.SERVERONLY); public static readonly CVarDef StatusMaxConnections = CVarDef.Create("status.max_connections", 5, CVar.SERVERONLY); diff --git a/Robust.Shared/Network/NetManager.Upnp.cs b/Robust.Shared/Network/NetManager.Upnp.cs new file mode 100644 index 000000000..6a80debe5 --- /dev/null +++ b/Robust.Shared/Network/NetManager.Upnp.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using Lidgren.Network; +using Robust.Shared.Log; + +namespace Robust.Shared.Network; + +public partial class NetManager +{ + private void InitUpnp() + { + var sawmill = Logger.GetSawmill("net.upnp"); + var port = Port; + + var peers = _netPeers.Select(p => p.Peer).Where(p => p.Configuration.EnableUPnP).ToArray(); + if (peers.Length == 0) + { + sawmill.Warning("Can't UPnP forward: No IPv4-compatible NetPeers available."); + return; + } + + // We DON'T want to hold up the main server on this! + new Thread(() => + { + try + { + foreach (var peer in peers) + { + // The way the NetUPnP code is written, we're doing guesswork anyway + // It seems to be that the assumption in regards to IPv6 is "what?" w/ UPnP???? + // ATTENTION FUTURE 20KDC (or anyone else who comes by): + // IF YOU GET IPv6 FOR REALSIES, WORK OUT HOW TO DEAL W/ THIS! + var upnp = peer.UPnP; + while (upnp.Status == UPnPStatus.Discovering) + { + // Sleep while the network thread does the work + NetUtility.Sleep(250); + } + + // Clear these forwarding rules because we don't want any OTHER SS14 servers on our network (or different local IP addresses of ourself) conflicting + upnp.DeleteForwardingRule(port, "UDP"); + upnp.DeleteForwardingRule(port, "TCP"); + var udpRes = upnp.ForwardPort(port, "RobustToolbox UDP", 0, "UDP"); + var tcpRes = upnp.ForwardPort(port, "RobustToolbox TCP", 0, "TCP"); + // Message needs to show in warning if something went wrong + var message = $"UPnP setup for port {port} on peer {peer.Configuration.LocalAddress} results: TCP {tcpRes}, UDP {udpRes}"; + if (tcpRes && udpRes) + { + sawmill.Info(message); + } + else + { + sawmill.Warning(message); + } + } + } + catch (Exception e) + { + sawmill.Warning($"UPnP threw an exception: {e}"); + } + }).Start(); + } + + private static bool UpnpCompatible(NetPeerConfiguration cfg) + { + return cfg.LocalAddress.AddressFamily == AddressFamily.InterNetwork || cfg.DualStack; + } +} diff --git a/Robust.Shared/Network/NetManager.cs b/Robust.Shared/Network/NetManager.cs index 945448335..f0512e10c 100644 --- a/Robust.Shared/Network/NetManager.cs +++ b/Robust.Shared/Network/NetManager.cs @@ -349,6 +349,9 @@ namespace Robust.Shared.Network config.DualStack = true; } + if (UpnpCompatible(config)) + config.EnableUPnP = true; + var peer = IsServer ? (NetPeer) new NetServer(config) : new NetClient(config); peer.Start(); _netPeers.Add(new NetPeerData(peer)); @@ -365,6 +368,9 @@ namespace Robust.Shared.Network Logger.WarningS("net", "IPv6 Dual Stack is enabled but no IPv6 addresses have been bound to. This will not work."); } + + if (_config.GetCVar(CVars.NetUPnP)) + InitUpnp(); } ///