diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 2386f15cec..05879f4573 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -61,13 +61,13 @@ namespace OpenRA public static event Action OnShellmapLoaded = () => { }; - public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true) + public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true) { - var connection = new NetworkConnection(host, port); + var connection = new NetworkConnection(endpoint); if (recordReplay) connection.StartRecording(() => { return TimestampedFilename(); }); - var om = new OrderManager(host, port, password, connection); + var om = new OrderManager(endpoint, password, connection); JoinInner(om); return om; } @@ -88,12 +88,12 @@ namespace OpenRA public static void JoinReplay(string replayFile) { - JoinInner(new OrderManager("", -1, "", new ReplayConnection(replayFile))); + JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile))); } static void JoinLocal() { - JoinInner(new OrderManager("", -1, "", new EchoConnection())); + JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection())); } // More accurate replacement for Environment.TickCount @@ -104,14 +104,14 @@ namespace OpenRA public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } } public static int LocalTick { get { return OrderManager.LocalFrameNumber; } } - public static event Action OnRemoteDirectConnect = (a, b) => { }; + public static event Action OnRemoteDirectConnect = _ => { }; public static event Action ConnectionStateChanged = _ => { }; static ConnectionState lastConnectionState = ConnectionState.PreConnecting; public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } } - public static void RemoteDirectConnect(string host, int port) + public static void RemoteDirectConnect(ConnectionTarget endpoint) { - OnRemoteDirectConnect(host, port); + OnRemoteDirectConnect(endpoint); } // Hacky workaround for orderManager visibility @@ -233,7 +233,7 @@ namespace OpenRA LobbyInfoChanged += lobbyReady; - om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), ""); + om = JoinServer(CreateLocalServer(mapUID), ""); } public static bool IsHost @@ -301,6 +301,7 @@ namespace OpenRA Log.AddChannel("graphics", "graphics.log"); Log.AddChannel("geoip", "geoip.log"); Log.AddChannel("nat", "nat.log"); + Log.AddChannel("client", "client.log"); var platforms = new[] { Settings.Game.Platform, "Default", null }; foreach (var p in platforms) @@ -384,7 +385,7 @@ namespace OpenRA LobbyInfoChanged = () => { }; ConnectionStateChanged = om => { }; BeforeGameStart = () => { }; - OnRemoteDirectConnect = (a, b) => { }; + OnRemoteDirectConnect = endpoint => { }; delayedActions = new ActionQueue(); Ui.ResetAll(); @@ -898,12 +899,19 @@ namespace OpenRA return ModData.ObjectCreator.CreateObject(name); } - public static void CreateServer(ServerSettings settings) + public static ConnectionTarget CreateServer(ServerSettings settings) { - server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer); + var endpoints = new List + { + new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort), + new IPEndPoint(IPAddress.Any, settings.ListenPort) + }; + server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer); + + return server.GetEndpointForLocalConnection(); } - public static int CreateLocalServer(string map) + public static ConnectionTarget CreateLocalServer(string map) { var settings = new ServerSettings() { @@ -912,9 +920,14 @@ namespace OpenRA AdvertiseOnline = false }; - server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local); + var endpoints = new List + { + new IPEndPoint(IPAddress.IPv6Loopback, 0), + new IPEndPoint(IPAddress.Loopback, 0) + }; + server = new Server.Server(endpoints, settings, ModData, ServerType.Local); - return server.Port; + return server.GetEndpointForLocalConnection(); } public static bool IsCurrentWorld(World world) diff --git a/OpenRA.Game/Network/Connection.cs b/OpenRA.Game/Network/Connection.cs index 7d7b3ff7f1..db29a17711 100644 --- a/OpenRA.Game/Network/Connection.cs +++ b/OpenRA.Game/Network/Connection.cs @@ -10,8 +10,11 @@ #endregion using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Net; using System.Net.Sockets; using System.Threading; using OpenRA.Server; @@ -30,12 +33,63 @@ namespace OpenRA.Network { int LocalClientId { get; } ConnectionState ConnectionState { get; } + IPEndPoint EndPoint { get; } + string ErrorMessage { get; } void Send(int frame, List orders); void SendImmediate(IEnumerable orders); void SendSync(int frame, byte[] syncData); void Receive(Action packetFn); } + public class ConnectionTarget + { + readonly DnsEndPoint[] endpoints; + + public ConnectionTarget() + { + endpoints = new[] { new DnsEndPoint("invalid", 0) }; + } + + public ConnectionTarget(string host, int port) + { + endpoints = new[] { new DnsEndPoint(host, port) }; + } + + public ConnectionTarget(IEnumerable endpoints) + { + this.endpoints = endpoints.ToArray(); + if (this.endpoints.Length == 0) + { + throw new ArgumentException("ConnectionTarget must have at least one address."); + } + } + + public IEnumerable GetConnectEndPoints() + { + return endpoints + .SelectMany(e => + { + try + { + return Dns.GetHostAddresses(e.Host) + .Select(a => new IPEndPoint(a, e.Port)); + } + catch (Exception) + { + return Enumerable.Empty(); + } + }) + .ToList(); + } + + public override string ToString() + { + return endpoints + .Select(e => "{0}:{1}".F(e.Host, e.Port)) + .JoinWith("/"); + } + } + class EchoConnection : IConnection { protected struct ReceivedPacket @@ -57,6 +111,16 @@ namespace OpenRA.Network get { return ConnectionState.PreConnecting; } } + public virtual IPEndPoint EndPoint + { + get { throw new NotSupportedException("An echo connection doesn't have an endpoint"); } + } + + public virtual string ErrorMessage + { + get { return null; } + } + public virtual void Send(int frame, List orders) { var ms = new MemoryStream(); @@ -138,35 +202,100 @@ namespace OpenRA.Network sealed class NetworkConnection : EchoConnection { - readonly TcpClient tcp; + readonly ConnectionTarget target; + TcpClient tcp; + IPEndPoint endpoint; readonly List queuedSyncPackets = new List(); volatile ConnectionState connectionState = ConnectionState.Connecting; volatile int clientId; bool disposed; + string errorMessage; - public NetworkConnection(string host, int port) + public override IPEndPoint EndPoint { get { return endpoint; } } + + public override string ErrorMessage { get { return errorMessage; } } + + public NetworkConnection(ConnectionTarget target) { - try + this.target = target; + new Thread(NetworkConnectionConnect) { - tcp = new TcpClient(host, port) { NoDelay = true }; + Name = "{0} (connect to {1})".F(GetType().Name, target), + IsBackground = true + }.Start(); + } + + void NetworkConnectionConnect() + { + var queue = new BlockingCollection(); + + var atLeastOneEndpoint = false; + foreach (var endpoint in target.GetConnectEndPoints()) + { + atLeastOneEndpoint = true; + new Thread(() => + { + try + { + var client = new TcpClient(endpoint.AddressFamily) { NoDelay = true }; + client.Connect(endpoint.Address, endpoint.Port); + + try + { + queue.Add(client); + } + catch (InvalidOperationException) + { + // Another connection was faster, close this one. + client.Close(); + } + } + catch (Exception ex) + { + errorMessage = "Failed to connect to {0}".F(endpoint); + Log.Write("client", "Failed to connect to {0}: {1}".F(endpoint, ex.Message)); + } + }) + { + Name = "{0} (connect to {1})".F(GetType().Name, endpoint), + IsBackground = true + }.Start(); + } + + if (!atLeastOneEndpoint) + { + errorMessage = "Failed to resolve addresses for {0}".F(target); + connectionState = ConnectionState.NotConnected; + } + + // Wait up to 5s for a successful connection. This should hopefully be enough because such high latency makes the game unplayable anyway. + else if (queue.TryTake(out tcp, 5000)) + { + // Copy endpoint here to have it even after getting disconnected. + endpoint = (IPEndPoint)tcp.Client.RemoteEndPoint; + new Thread(NetworkConnectionReceive) { - Name = GetType().Name + " " + host + ":" + port, + Name = "{0} (receive from {1})".F(GetType().Name, tcp.Client.RemoteEndPoint), IsBackground = true - }.Start(tcp.GetStream()); + }.Start(); } - catch + else { connectionState = ConnectionState.NotConnected; } + + // Close all unneeded connections in the queue and make sure new ones are closed on the connect thread. + queue.CompleteAdding(); + foreach (var client in queue) + client.Close(); } - void NetworkConnectionReceive(object networkStreamObject) + void NetworkConnectionReceive() { try { - var networkStream = (NetworkStream)networkStreamObject; - var reader = new BinaryReader(networkStream); + var reader = new BinaryReader(tcp.GetStream()); var handshakeProtocol = reader.ReadInt32(); if (handshakeProtocol != ProtocolVersion.Handshake) @@ -187,7 +316,11 @@ namespace OpenRA.Network AddPacket(new ReceivedPacket { FromClient = client, Data = buf }); } } - catch { } + catch (Exception ex) + { + errorMessage = "Connection to {0} failed".F(endpoint); + Log.Write("client", "Connection to {0} failed: {1}".F(endpoint, ex.Message)); + } finally { connectionState = ConnectionState.NotConnected; diff --git a/OpenRA.Game/Network/OrderManager.cs b/OpenRA.Game/Network/OrderManager.cs index cae2df2175..1c4e5ece81 100644 --- a/OpenRA.Game/Network/OrderManager.cs +++ b/OpenRA.Game/Network/OrderManager.cs @@ -28,11 +28,10 @@ namespace OpenRA.Network public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } } public World World; - public readonly string Host; - public readonly int Port; + public readonly ConnectionTarget Endpoint; public readonly string Password = ""; - public string ServerError = "Server is not responding"; + public string ServerError = null; public bool AuthenticationFailed = false; public ExternalMod ServerExternalMod = null; @@ -80,10 +79,9 @@ namespace OpenRA.Network Connection.Send(i, new List()); } - public OrderManager(string host, int port, string password, IConnection conn) + public OrderManager(ConnectionTarget endpoint, string password, IConnection conn) { - Host = host; - Port = port; + Endpoint = endpoint; Password = password; Connection = conn; syncReport = new SyncReport(this); diff --git a/OpenRA.Game/Network/ReplayConnection.cs b/OpenRA.Game/Network/ReplayConnection.cs index 2f18c0c5d1..01c702d86d 100644 --- a/OpenRA.Game/Network/ReplayConnection.cs +++ b/OpenRA.Game/Network/ReplayConnection.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using OpenRA.FileFormats; using OpenRA.Primitives; @@ -32,6 +33,13 @@ namespace OpenRA.Network public int LocalClientId { get { return -1; } } public ConnectionState ConnectionState { get { return ConnectionState.Connected; } } + public IPEndPoint EndPoint + { + get { throw new NotSupportedException("A replay connection doesn't have an endpoint"); } + } + + public string ErrorMessage { get { return null; } } + public readonly int TickCount; public readonly int FinalGameTick; public readonly bool IsValid; diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 0ef2a61156..ccc5e801f8 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -18,7 +18,6 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; -using OpenRA.Graphics; using OpenRA.Network; using OpenRA.Primitives; using OpenRA.Support; @@ -43,8 +42,6 @@ namespace OpenRA.Server { public readonly string TwoHumansRequiredText = "This server requires at least two human players to start a match."; - public readonly IPAddress Ip; - public readonly int Port; public readonly MersenneTwister Random = new MersenneTwister(); public readonly ServerType Type; @@ -64,7 +61,7 @@ namespace OpenRA.Server public GameSave GameSave = null; readonly int randomSeed; - readonly TcpListener listener; + readonly List listeners = new List(); readonly TypeDictionary serverTraits = new TypeDictionary(); readonly PlayerDatabase playerDatabase; @@ -129,15 +126,43 @@ namespace OpenRA.Server t.GameEnded(this); } - public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData, ServerType type) + public Server(List endpoints, ServerSettings settings, ModData modData, ServerType type) { Log.AddChannel("server", "server.log", true); - listener = new TcpListener(endpoint); - listener.Start(); - var localEndpoint = (IPEndPoint)listener.LocalEndpoint; - Ip = localEndpoint.Address; - Port = localEndpoint.Port; + SocketException lastException = null; + var checkReadServer = new List(); + foreach (var endpoint in endpoints) + { + var listener = new TcpListener(endpoint); + try + { + try + { + listener.Server.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 1); + } + catch (Exception ex) + { + if (ex is SocketException || ex is ArgumentException) + Log.Write("server", "Failed to set socket option on {0}: {1}", endpoint.ToString(), ex.Message); + else + throw; + } + + listener.Start(); + listeners.Add(listener); + checkReadServer.Add(listener.Server); + } + catch (SocketException ex) + { + lastException = ex; + Log.Write("server", "Failed to listen on {0}: {1}", endpoint.ToString(), ex.Message); + } + } + + if (listeners.Count == 0) + throw lastException; + Type = type; Settings = settings; @@ -186,7 +211,7 @@ namespace OpenRA.Server { var checkRead = new List(); if (State == ServerState.WaitingPlayers) - checkRead.Add(listener.Server); + checkRead.AddRange(checkReadServer); checkRead.AddRange(Conns.Select(c => c.Socket)); checkRead.AddRange(PreConns.Select(c => c.Socket)); @@ -205,9 +230,10 @@ namespace OpenRA.Server foreach (var s in checkRead) { - if (s == listener.Server) + var serverIndex = checkReadServer.IndexOf(s); + if (serverIndex >= 0) { - AcceptConnection(); + AcceptConnection(listeners[serverIndex]); continue; } @@ -246,9 +272,14 @@ namespace OpenRA.Server PreConns.Clear(); Conns.Clear(); - try { listener.Stop(); } - catch { } - }) { IsBackground = true }.Start(); + + foreach (var listener in listeners) + { + try { listener.Stop(); } + catch { } + } + }) + { IsBackground = true }.Start(); } int nextPlayerIndex; @@ -257,7 +288,7 @@ namespace OpenRA.Server return nextPlayerIndex++; } - void AcceptConnection() + void AcceptConnection(TcpListener listener) { Socket newSocket; @@ -956,7 +987,8 @@ namespace OpenRA.Server public void StartGame() { - listener.Stop(); + foreach (var listener in listeners) + listener.Stop(); Console.WriteLine("[{0}] Game started", DateTime.Now.ToString(Settings.TimestampFormat)); @@ -1018,5 +1050,22 @@ namespace OpenRA.Server }); } } + + public ConnectionTarget GetEndpointForLocalConnection() + { + var endpoints = new List(); + foreach (var listener in listeners) + { + var endpoint = (IPEndPoint)listener.LocalEndpoint; + if (IPAddress.IPv6Any.Equals(endpoint.Address)) + endpoints.Add(new DnsEndPoint(IPAddress.IPv6Loopback.ToString(), endpoint.Port)); + else if (IPAddress.Any.Equals(endpoint.Address)) + endpoints.Add(new DnsEndPoint(IPAddress.Loopback.ToString(), endpoint.Port)); + else + endpoints.Add(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port)); + } + + return new ConnectionTarget(endpoints); + } } } diff --git a/OpenRA.Game/Support/LaunchArguments.cs b/OpenRA.Game/Support/LaunchArguments.cs index b10d9bdb57..a3894724b9 100644 --- a/OpenRA.Game/Support/LaunchArguments.cs +++ b/OpenRA.Game/Support/LaunchArguments.cs @@ -9,6 +9,9 @@ */ #endregion +using System; +using OpenRA.Network; + namespace OpenRA { public class LaunchArguments @@ -38,17 +41,28 @@ namespace OpenRA FieldLoader.LoadField(this, f.Name, args.GetValue("Launch" + "." + f.Name, "")); } - public string GetConnectAddress() + public ConnectionTarget GetConnectEndPoint() { - var connect = string.Empty; + try + { + Uri uri; + if (!string.IsNullOrEmpty(URI)) + uri = new Uri(URI); + else if (!string.IsNullOrEmpty(Connect)) + uri = new Uri("tcp://" + Connect); + else + return null; - if (!string.IsNullOrEmpty(Connect)) - connect = Connect; - - if (!string.IsNullOrEmpty(URI)) - connect = URI.Substring(URI.IndexOf("://", System.StringComparison.Ordinal) + 3).TrimEnd('/'); - - return connect; + if (uri.IsAbsoluteUri) + return new ConnectionTarget(uri.Host, uri.Port); + else + return null; + } + catch (Exception ex) + { + Log.Write("client", "Failed to parse Launch.URI or Launch.Connect: {0}", ex.Message); + return null; + } } } } diff --git a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs index efaf2a9dee..1d992930b5 100644 --- a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs @@ -53,19 +53,12 @@ namespace OpenRA.Mods.Common.LoadScreens } // Join a server directly - var connect = Launch.GetConnectAddress(); - if (!string.IsNullOrEmpty(connect)) + var connect = Launch.GetConnectEndPoint(); + if (connect != null) { - var parts = connect.Split(':'); - - if (parts.Length == 2) - { - var host = parts[0]; - var port = Exts.ParseIntegerInvariant(parts[1]); - Game.LoadShellMap(); - Game.RemoteDirectConnect(host, port); - return; - } + Game.LoadShellMap(); + Game.RemoteDirectConnect(connect); + return; } // Start a map directly diff --git a/OpenRA.Mods.Common/ServerTraits/MasterServerPinger.cs b/OpenRA.Mods.Common/ServerTraits/MasterServerPinger.cs index cbb22cbfc8..d92edeeb5d 100644 --- a/OpenRA.Mods.Common/ServerTraits/MasterServerPinger.cs +++ b/OpenRA.Mods.Common/ServerTraits/MasterServerPinger.cs @@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Server public void ServerStarted(S server) { - if (!server.Ip.Equals(IPAddress.Loopback) && LanGameBeacon != null) + if (server.Type != ServerType.Local && LanGameBeacon != null) LanGameBeacon.Start(); } diff --git a/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs index 0638a04821..e2c8aee517 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs @@ -11,7 +11,6 @@ using System; using OpenRA.Network; -using OpenRA.Primitives; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic @@ -49,7 +48,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic } [ObjectCreator.UseCtor] - public ConnectionLogic(Widget widget, string host, int port, Action onConnect, Action onAbort, Action onRetry) + public ConnectionLogic(Widget widget, ConnectionTarget endpoint, Action onConnect, Action onAbort, Action onRetry) { this.onConnect = onConnect; this.onAbort = onAbort; @@ -61,18 +60,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic panel.Get("ABORT_BUTTON").OnClick = () => { CloseWindow(); onAbort(); }; widget.Get("CONNECTING_DESC").GetText = () => - "Connecting to {0}:{1}...".F(host, port); + "Connecting to {0}...".F(endpoint); } - public static void Connect(string host, int port, string password, Action onConnect, Action onAbort) + public static void Connect(ConnectionTarget endpoint, string password, Action onConnect, Action onAbort) { - Game.JoinServer(host, port, password); - Action onRetry = newPassword => Connect(host, port, newPassword, onConnect, onAbort); + Game.JoinServer(endpoint, password); + Action onRetry = newPassword => Connect(endpoint, newPassword, onConnect, onAbort); Ui.OpenWindow("CONNECTING_PANEL", new WidgetArgs() { - { "host", host }, - { "port", port }, + { "endpoint", endpoint }, { "onConnect", onConnect }, { "onAbort", onAbort }, { "onRetry", onRetry } @@ -105,10 +103,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic }; widget.Get("CONNECTING_DESC").GetText = () => - "Could not connect to {0}:{1}".F(orderManager.Host, orderManager.Port); + "Could not connect to {0}".F(orderManager.Endpoint); var connectionError = widget.Get("CONNECTION_ERROR"); - connectionError.GetText = () => orderManager.ServerError; + connectionError.GetText = () => orderManager.ServerError ?? orderManager.Connection.ErrorMessage ?? "Unknown error"; var panelTitle = widget.Get("TITLE"); panelTitle.GetText = () => orderManager.AuthenticationFailed ? "Password Required" : "Connection Failed"; @@ -165,7 +163,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic switchButton.OnClick = () => { - var launchCommand = "Launch.Connect=" + orderManager.Host + ":" + orderManager.Port; + var launchCommand = "Launch.URI={0}".F(new UriBuilder("tcp", orderManager.Connection.EndPoint.Address.ToString(), orderManager.Connection.EndPoint.Port)); Game.SwitchToExternalMod(orderManager.ServerExternalMod, new[] { launchCommand }, () => { orderManager.ServerError = "Failed to switch mod."; diff --git a/OpenRA.Mods.Common/Widgets/Logic/DirectConnectLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/DirectConnectLogic.cs index 27d33b15c0..593888dceb 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/DirectConnectLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/DirectConnectLogic.cs @@ -10,6 +10,7 @@ #endregion using System; +using OpenRA.Network; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic @@ -19,15 +20,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic static readonly Action DoNothing = () => { }; [ObjectCreator.UseCtor] - public DirectConnectLogic(Widget widget, Action onExit, Action openLobby, string directConnectHost, int directConnectPort) + public DirectConnectLogic(Widget widget, Action onExit, Action openLobby, ConnectionTarget directConnectEndPoint) { var panel = widget; var ipField = panel.Get("IP"); var portField = panel.Get("PORT"); - var last = Game.Settings.Player.LastServer.Split(':'); - ipField.Text = last.Length > 1 ? last[0] : "localhost"; - portField.Text = last.Length == 2 ? last[1] : "1234"; + var text = Game.Settings.Player.LastServer; + var last = text.LastIndexOf(':'); + if (last < 0) + { + ipField.Text = "localhost"; + portField.Text = "1234"; + } + else + { + ipField.Text = text.Substring(0, last); + portField.Text = text.Substring(last + 1); + } panel.Get("JOIN_BUTTON").OnClick = () => { @@ -36,19 +46,19 @@ namespace OpenRA.Mods.Common.Widgets.Logic Game.Settings.Player.LastServer = "{0}:{1}".F(ipField.Text, port); Game.Settings.Save(); - ConnectionLogic.Connect(ipField.Text, port, "", () => { Ui.CloseWindow(); openLobby(); }, DoNothing); + ConnectionLogic.Connect(new ConnectionTarget(ipField.Text, port), "", () => { Ui.CloseWindow(); openLobby(); }, DoNothing); }; panel.Get("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; - if (directConnectHost != null) + if (directConnectEndPoint != null) { // The connection window must be opened at the end of the tick for the widget hierarchy to // work out, but we also want to prevent the server browser from flashing visible for one tick. widget.Visible = false; Game.RunAfterTick(() => { - ConnectionLogic.Connect(directConnectHost, directConnectPort, "", () => { Ui.CloseWindow(); openLobby(); }, DoNothing); + ConnectionLogic.Connect(directConnectEndPoint, "", () => { Ui.CloseWindow(); openLobby(); }, DoNothing); widget.Visible = true; }); } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs index 0d170d9e68..a9022ceee9 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs @@ -71,8 +71,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic // It's not clear why this is needed here, but not in the other places that load maps. Game.RunAfterTick(() => { - ConnectionLogic.Connect(System.Net.IPAddress.Loopback.ToString(), - Game.CreateLocalServer(uid), "", + ConnectionLogic.Connect(Game.CreateLocalServer(uid), "", () => Game.LoadEditor(uid), () => { Game.CloseServer(); onExit(); }); }); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs index c5db056adc..0957bfa0f5 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs @@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic }); }; - Action onRetry = password => ConnectionLogic.Connect(om.Host, om.Port, password, onConnect, onExit); + Action onRetry = password => ConnectionLogic.Connect(om.Endpoint, password, onConnect, onExit); var switchPanel = om.ServerExternalMod != null ? "CONNECTION_SWITCHMOD_PANEL" : "CONNECTIONFAILED_PANEL"; Ui.OpenWindow(switchPanel, new WidgetArgs() diff --git a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs index 807f5ab217..39ff958223 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs @@ -17,6 +17,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using OpenRA.Network; using OpenRA.Primitives; using OpenRA.Widgets; @@ -298,22 +299,20 @@ namespace OpenRA.Mods.Common.Widgets.Logic button.AttachPanel(newsPanel, () => newsOpen = false); } - void OnRemoteDirectConnect(string host, int port) + void OnRemoteDirectConnect(ConnectionTarget endpoint) { SwitchMenu(MenuType.None); Ui.OpenWindow("MULTIPLAYER_PANEL", new WidgetArgs { { "onStart", RemoveShellmapUI }, { "onExit", () => SwitchMenu(MenuType.Main) }, - { "directConnectHost", host }, - { "directConnectPort", port }, + { "directConnectEndPoint", endpoint }, }); } void LoadMapIntoEditor(string uid) { - ConnectionLogic.Connect(IPAddress.Loopback.ToString(), - Game.CreateLocalServer(uid), + ConnectionLogic.Connect(Game.CreateLocalServer(uid), "", () => { Game.LoadEditor(uid); }, () => { Game.CloseServer(); SwitchMenu(MenuType.MapEditor); }); @@ -425,8 +424,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic Game.Settings.Server.Map = map; Game.Settings.Save(); - ConnectionLogic.Connect(IPAddress.Loopback.ToString(), - Game.CreateLocalServer(map), + ConnectionLogic.Connect(Game.CreateLocalServer(map), "", OpenSkirmishLobbyPanel, () => { Game.CloseServer(); SwitchMenu(MenuType.Main); }); @@ -460,8 +458,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { { "onStart", () => { RemoveShellmapUI(); lastGameState = MenuPanel.Multiplayer; } }, { "onExit", () => SwitchMenu(MenuType.Main) }, - { "directConnectHost", null }, - { "directConnectPort", 0 }, + { "directConnectEndPoint", null }, }); } diff --git a/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs index 5e0efae9c8..1999281f2e 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MultiplayerLogic.cs @@ -25,7 +25,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly ServerListLogic serverListLogic; [ObjectCreator.UseCtor] - public MultiplayerLogic(Widget widget, ModData modData, Action onStart, Action onExit, string directConnectHost, int directConnectPort) + public MultiplayerLogic(Widget widget, ModData modData, Action onStart, Action onExit, ConnectionTarget directConnectEndPoint) { // MultiplayerLogic is a superset of the ServerListLogic // but cannot be a direct subclass because it needs to pass object-level state to the constructor @@ -41,8 +41,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { { "openLobby", OpenLobby }, { "onExit", DoNothing }, - { "directConnectHost", null }, - { "directConnectPort", 0 }, + { "directConnectEndPoint", null }, }); }; @@ -61,7 +60,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic widget.Get("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; - if (directConnectHost != null) + if (directConnectEndPoint != null) { // The connection window must be opened at the end of the tick for the widget hierarchy to // work out, but we also want to prevent the server browser from flashing visible for one tick. @@ -72,8 +71,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { { "openLobby", OpenLobby }, { "onExit", DoNothing }, - { "directConnectHost", directConnectHost }, - { "directConnectPort", directConnectPort }, + { "directConnectEndPoint", directConnectEndPoint }, }); widget.Visible = true; @@ -93,8 +91,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { { "onStart", onStart }, { "onExit", onExit }, - { "directConnectHost", null }, - { "directConnectPort", 0 }, + { "directConnectEndPoint", null }, }); Game.Disconnect(); @@ -116,7 +113,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var host = server.Address.Split(':')[0]; var port = Exts.ParseIntegerInvariant(server.Address.Split(':')[1]); - ConnectionLogic.Connect(host, port, "", OpenLobby, DoNothing); + ConnectionLogic.Connect(new ConnectionTarget(host, port), "", OpenLobby, DoNothing); } bool disposed; diff --git a/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs index 386060a314..f4af9ded43 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ServerCreationLogic.cs @@ -11,7 +11,6 @@ using System; using System.Linq; -using System.Net; using OpenRA.Network; using OpenRA.Primitives; using OpenRA.Widgets; @@ -199,7 +198,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic // Create and join the server try { - Game.CreateServer(settings); + var endpoint = Game.CreateServer(settings); + + Ui.CloseWindow(); + ConnectionLogic.Connect(endpoint, password, onCreate, onExit); } catch (System.Net.Sockets.SocketException e) { @@ -212,11 +214,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic message += "\nError is: \"{0}\" ({1})".F(e.Message, e.ErrorCode); ConfirmationDialogs.ButtonPrompt("Server Creation Failed", message, onCancel: () => { }, cancelText: "Back"); - return; } - - Ui.CloseWindow(); - ConnectionLogic.Connect(IPAddress.Loopback.ToString(), Game.Settings.Server.ListenPort, password, onCreate, onExit); } } } diff --git a/OpenRA.Server/Program.cs b/OpenRA.Server/Program.cs index ec4b4f29ea..07445e9718 100644 --- a/OpenRA.Server/Program.cs +++ b/OpenRA.Server/Program.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Threading; @@ -66,7 +67,9 @@ namespace OpenRA.Server settings.Map = modData.MapCache.ChooseInitialMap(settings.Map, new MersenneTwister()); - var server = new Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, modData, ServerType.Dedicated); + var endpoints = new List { new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort), new IPEndPoint(IPAddress.Any, settings.ListenPort) }; + var server = new Server(endpoints, settings, modData, ServerType.Dedicated); + GC.Collect(); while (true) {