diff --git a/Lidgren.Network/NetPeer.Internal.cs b/Lidgren.Network/NetPeer.Internal.cs index 78419d1a1..d5e07caf4 100644 --- a/Lidgren.Network/NetPeer.Internal.cs +++ b/Lidgren.Network/NetPeer.Internal.cs @@ -114,7 +114,13 @@ namespace Lidgren.Network iep = new IPEndPoint(m_configuration.LocalAddress, m_configuration.Port); EndPoint ep = (EndPoint)iep; - m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + m_socket = new Socket(ep.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + if (ep.AddressFamily == AddressFamily.InterNetworkV6) + { + // Disable IPv4 -> IPv6 mapping. + // SS14 handles IPv6 & IPv4 concurrently with a different net peer. + m_socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, true); + } m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize; m_socket.SendBufferSize = m_configuration.SendBufferSize; m_socket.Blocking = false; @@ -386,6 +392,7 @@ namespace Lidgren.Network int bytesReceived = 0; try { + var honk = this; bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote); } catch (SocketException sx) diff --git a/Lidgren.Network/NetPeer.cs b/Lidgren.Network/NetPeer.cs index 02d3b1fd9..dacafbbb2 100644 --- a/Lidgren.Network/NetPeer.cs +++ b/Lidgren.Network/NetPeer.cs @@ -2,6 +2,7 @@ using System; using System.Threading; using System.Collections.Generic; using System.Net; +using System.Net.Sockets; namespace Lidgren.Network { @@ -102,7 +103,14 @@ namespace Lidgren.Network m_connections = new List(); m_connectionLookup = new Dictionary(); m_handshakes = new Dictionary(); - m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0); + if (m_configuration.LocalAddress.AddressFamily == AddressFamily.InterNetworkV6) + { + m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.IPv6Any, 0); + } + else + { + m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0); + } m_status = NetPeerStatus.NotRunning; m_receivedFragmentGroups = new Dictionary>(); } diff --git a/Lidgren.Network/NetUtility.cs b/Lidgren.Network/NetUtility.cs index 37ba0392b..0471a8e2e 100644 --- a/Lidgren.Network/NetUtility.cs +++ b/Lidgren.Network/NetUtility.cs @@ -85,7 +85,8 @@ namespace Lidgren.Network IPAddress ipAddress = null; if (IPAddress.TryParse(ipOrHost, out ipAddress)) { - if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + if (ipAddress.AddressFamily == AddressFamily.InterNetwork + || ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { callback(ipAddress); return; @@ -123,16 +124,23 @@ namespace Lidgren.Network } // check each entry for a valid IP address + IPAddress bestAddress = null; foreach (IPAddress ipCurrent in entry.AddressList) { - if (ipCurrent.AddressFamily == AddressFamily.InterNetwork) + // Prefer IPv6 addresses. + if (ipCurrent.AddressFamily == AddressFamily.InterNetworkV6) { callback(ipCurrent); return; } + + if (ipCurrent.AddressFamily == AddressFamily.InterNetwork) + { + bestAddress = ipCurrent; + } } - callback(null); + callback(bestAddress); }, null); } catch (SocketException ex) @@ -161,7 +169,8 @@ namespace Lidgren.Network IPAddress ipAddress = null; if (IPAddress.TryParse(ipOrHost, out ipAddress)) { - if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + if (ipAddress.AddressFamily == AddressFamily.InterNetwork + || ipAddress.AddressFamily == AddressFamily.InterNetworkV6) return ipAddress; throw new ArgumentException("This method will not currently resolve other than ipv4 addresses"); } @@ -175,13 +184,22 @@ namespace Lidgren.Network return null; // check each entry for a valid IP address + IPAddress bestAddress = null; foreach (IPAddress ipCurrent in entry.AddressList) { - if (ipCurrent.AddressFamily == AddressFamily.InterNetwork) + if (ipCurrent.AddressFamily == AddressFamily.InterNetworkV6) + { return ipCurrent; + } + + // Prefer IPv6 addresses. + if (ipCurrent.AddressFamily == AddressFamily.InterNetwork) + { + bestAddress = ipCurrent; + } } - return null; + return bestAddress; } catch (SocketException ex) { @@ -391,6 +409,11 @@ namespace Lidgren.Network /// public static bool IsLocal(IPAddress remote) { + if (remote.AddressFamily == AddressFamily.InterNetworkV6) + { + // TODO: Can this be made to work? Do we even care for SS14? + return false; + } IPAddress mask; IPAddress local = GetMyAddress(out mask); diff --git a/SS14.Client/BaseClient.cs b/SS14.Client/BaseClient.cs index 2a75e1462..fcc0a9240 100644 --- a/SS14.Client/BaseClient.cs +++ b/SS14.Client/BaseClient.cs @@ -65,11 +65,14 @@ namespace SS14.Client /// public void ConnectToServer(string ip, ushort port) { + if (RunLevel == ClientRunLevel.Connecting) + { + _net.Shutdown("Client mashing that connect button."); + Reset(); + } DebugTools.Assert(RunLevel < ClientRunLevel.Connecting); DebugTools.Assert(!_net.IsConnected); - _net.Startup(); - OnRunLevelChanged(ClientRunLevel.Connecting); _net.ClientConnect(ip, port, PlayerNameOverride ?? _configManager.GetCVar("player.name")); } diff --git a/SS14.Client/State/States/MainMenu.cs b/SS14.Client/State/States/MainMenu.cs index 1501e10ed..ac1f7543f 100644 --- a/SS14.Client/State/States/MainMenu.cs +++ b/SS14.Client/State/States/MainMenu.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Text.RegularExpressions; using SS14.Client.Input; using SS14.Client.Interfaces; using SS14.Client.Interfaces.UserInterface; @@ -32,6 +33,9 @@ namespace SS14.Client.State.States private OptionsMenu OptionsMenu; + // ReSharper disable once InconsistentNaming + private static readonly Regex IPv6Regex = new Regex(@"\[(.*:.*:.*)](?::(\d+))?"); + /// public override void Startup() { @@ -96,7 +100,8 @@ namespace SS14.Client.State.States } catch (ArgumentException e) { - userInterfaceManager.Popup($"Unable to resolve address: {e.Message}", "Invalid IP"); + userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error."); + Logger.Warning(e.ToString()); } } @@ -110,6 +115,21 @@ namespace SS14.Client.State.States private void ParseAddress(string address, out string ip, out ushort port) { + var match6 = IPv6Regex.Match(address); + if (match6 != Match.Empty) + { + ip = match6.Groups[1].Value; + if (!match6.Groups[2].Success) + { + port = _client.DefaultPort; + } + else if (!ushort.TryParse(match6.Groups[2].Value, out port)) + { + throw new ArgumentException("Not a valid port."); + } + + return; + } // See if the IP includes a port. var split = address.Split(':'); ip = address; diff --git a/SS14.Server/BaseServer.cs b/SS14.Server/BaseServer.cs index b65217b00..c643a0324 100644 --- a/SS14.Server/BaseServer.cs +++ b/SS14.Server/BaseServer.cs @@ -169,18 +169,13 @@ namespace SS14.Server try { netMan.Initialize(true); - netMan.Startup(); - } - catch (System.Net.Sockets.SocketException) - { - var port = netMan.Port; - Logger.Fatal($"Unable to setup networking manager. Check port {port} is not already in use!, shutting down..."); - Environment.Exit(1); + netMan.StartServer(); } catch (Exception e) { - Logger.Fatal($"Unable to setup networking manager. Unknown exception: {e}, shutting down..."); - Environment.Exit(1); + var port = netMan.Port; + Logger.Fatal("Unable to setup networking manager. Check port {0} is not already in use and that all binding addresses are correct!\n{1}", port, e); + return true; } // Set up the VFS diff --git a/SS14.Server/Console/Commands/PlayerCommands.cs b/SS14.Server/Console/Commands/PlayerCommands.cs index c4495e628..e07702088 100644 --- a/SS14.Server/Console/Commands/PlayerCommands.cs +++ b/SS14.Server/Console/Commands/PlayerCommands.cs @@ -64,17 +64,17 @@ namespace SS14.Server.Console.Commands var sb = new StringBuilder(); var players = IoCManager.Resolve().GetAllPlayers(); - sb.AppendLine($"{"Player Name",20}{"IP Address",16}{"Status",12}{"Playing Time",14}{"Ping",9}"); - sb.AppendLine("-----------------------------------------------------------------"); + sb.AppendLine($"{"Player Name",20} {"Status",12} {"Playing Time",14} {"Ping",9} {"IP EndPoint",20}"); + sb.AppendLine("-------------------------------------------------------------------------------"); - foreach (IPlayerSession p in players) + foreach (var p in players) { - sb.Append($" {p.Name,20}"); - sb.AppendLine(string.Format(" {0,21}{1,12}{2,14}{3,9}", + sb.AppendLine(string.Format("{4,20} {1,12} {2,14:hh\\:mm\\:ss} {3,9} {0,20}", p.ConnectedClient.RemoteEndPoint, p.Status.ToString(), - (DateTime.Now - p.ConnectedTime).ToString(@"hh\:mm\:ss"), - p.ConnectedClient.Ping + "ms")); + DateTime.Now - p.ConnectedTime, + p.ConnectedClient.Ping + "ms", + p.Name)); } shell.SendText(player, sb.ToString()); diff --git a/SS14.Server/server_config.toml b/SS14.Server/server_config.toml index 66937126c..a68a97aeb 100644 --- a/SS14.Server/server_config.toml +++ b/SS14.Server/server_config.toml @@ -8,6 +8,7 @@ enabled = false [net] tickrate = 60 port = 1212 +bindto = "::,0.0.0.0" [game] hostname = "MyServer" diff --git a/SS14.Shared/Interfaces/Network/INetManager.cs b/SS14.Shared/Interfaces/Network/INetManager.cs index 0098a62fe..8a7487cf1 100644 --- a/SS14.Shared/Interfaces/Network/INetManager.cs +++ b/SS14.Shared/Interfaces/Network/INetManager.cs @@ -58,7 +58,7 @@ namespace SS14.Shared.Interfaces.Network /// /// Starts the server running and listening on the port. /// - void Startup(); + void StartServer(); /// /// Shuts down this peer, disconnecting all channels. @@ -66,12 +66,6 @@ namespace SS14.Shared.Interfaces.Network /// String describing why the peer was shut down. void Shutdown(string reason); - /// - /// Restarts this peer, disconnecting all channels. - /// - /// String describing why the peer was restarted. - void Restart(string reason); - /// /// Process all queued packets. Should be called often. /// diff --git a/SS14.Shared/Network/NetManager.cs b/SS14.Shared/Network/NetManager.cs index 66745abc8..a7a0cc09f 100644 --- a/SS14.Shared/Network/NetManager.cs +++ b/SS14.Shared/Network/NetManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Sockets; using System.Threading; using Lidgren.Network; using SS14.Shared.Configuration; @@ -37,10 +38,10 @@ namespace SS14.Shared.Network /// private readonly Dictionary _channels = new Dictionary(); - private readonly Dictionary _assignedSessions = new Dictionary(); + private readonly Dictionary _assignedSessions = + new Dictionary(); - [Dependency] - private readonly IConfigurationManager _config; + [Dependency] private readonly IConfigurationManager _config; /// /// Holds lookup table for NetMessage.Id -> NetMessage.Type @@ -53,9 +54,9 @@ namespace SS14.Shared.Network private readonly StringTable _strings = new StringTable(); /// - /// The instance of the net server. + /// The list of network peers we are listening on. /// - private NetPeer _netPeer; + private readonly List _netPeers = new List(); /// public int Port => _config.GetCVar("net.port"); @@ -67,11 +68,31 @@ namespace SS14.Shared.Network public bool IsClient => !IsServer; /// - public bool IsConnected => _netPeer != null && _netPeer.ConnectionsCount > 0; + public bool IsConnected => _netPeers.Any(p => p.ConnectionsCount > 0); - public bool IsRunning => _netPeer != null; + public bool IsRunning => _netPeers.Count != 0; - public NetworkStats Statistics => new NetworkStats(_netPeer.Statistics); + public NetworkStats Statistics + { + get + { + var sentPackets = 0; + var sentBytes = 0; + var recvPackets = 0; + var recvBytes = 0; + + foreach (var peer in _netPeers) + { + var netPeerStatistics = peer.Statistics; + sentPackets += netPeerStatistics.SentPackets; + sentBytes += netPeerStatistics.SentBytes; + recvPackets += netPeerStatistics.ReceivedPackets; + recvBytes += netPeerStatistics.ReceivedBytes; + } + + return new NetworkStats(sentBytes, recvBytes, sentPackets, recvPackets); + } + } /// public IEnumerable Channels => _channels.Values; @@ -81,11 +102,37 @@ namespace SS14.Shared.Network public IReadOnlyDictionary CallbackAudit => _callbacks; + /// + public INetChannel ServerChannel + { + get + { + DebugTools.Assert(IsClient); + + if (_netPeers.Count == 0) + { + return null; + } + + var peer = _netPeers[0]; + if (peer.ConnectionsCount == 0) + { + return null; + } + + return GetChannel(peer.Connections[0]); + } + } + + private bool _initialized; + /// public void Initialize(bool isServer) { - if (_netPeer != null) - throw new InvalidOperationException("[NET] NetManager has already been initialized."); + if (_initialized) + { + throw new InvalidOperationException("NetManager has already been initialized."); + } IsServer = isServer; @@ -99,6 +146,11 @@ namespace SS14.Shared.Network _config.RegisterCVar("net.interpolation", 0.1f, CVar.ARCHIVE); _config.RegisterCVar("net.rate", 10240, CVar.REPLICATED | CVar.ARCHIVE); } + else + { + // That's comma-separated, btw. + _config.RegisterCVar("net.bindto", "0.0.0.0,::", CVar.ARCHIVE); + } #if DEBUG _config.RegisterCVar("net.fakelag", false, CVar.CHEAT); @@ -107,22 +159,166 @@ namespace SS14.Shared.Network _config.RegisterCVar("net.fakelagrand", 0.0f, CVar.CHEAT); #endif - _strings.Initialize(this, () => - { - OnConnected(ServerChannel); - }); + _strings.Initialize(this, () => { OnConnected(ServerChannel); }); + + _initialized = true; } - public void Startup() + public void StartServer() + { + DebugTools.Assert(IsServer); + DebugTools.Assert(!IsRunning); + + var binds = _config.GetCVar("net.bindto").Split(','); + + foreach (var bindAddress in binds) + { + if (!IPAddress.TryParse(bindAddress.Trim(), out var address)) + { + throw new InvalidOperationException("Not a valid IPv4 or IPv6 address"); + } + + var config = _getBaseNetPeerConfig(); + config.LocalAddress = address; + config.Port = Port; + config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); + + var peer = new NetPeer(config); + peer.Start(); + _netPeers.Add(peer); + } + + if (_netPeers.Count == 0) + { + Logger.WarningS("net", + "Exactly 0 addresses have been bound to, nothing will be able to connect to the server."); + } + } + + public void Dispose() + { + Shutdown("Network manager getting disposed."); + } + + /// + public void Shutdown(string reason) + { + foreach (var kvChannel in _channels) + DisconnectChannel(kvChannel.Value, reason); + + // request shutdown of the netPeer + _netPeers.ForEach(p => p.Shutdown(reason)); + _netPeers.Clear(); + + // wait for the network thread to finish its work (like flushing packets and gracefully disconnecting) + // Lidgren does not expose the thread, so we can't join or or anything + // pretty much have to poll every so often and wait for it to finish before continuing + // when the network thread is finished, it will change status from ShutdownRequested to NotRunning + while (_netPeers.Any(p => p.Status == NetPeerStatus.ShutdownRequested)) + { + // sleep the thread for an arbitrary length so it isn't spinning in the while loop as much + Thread.Sleep(50); + } + + _strings.Reset(); + } + + public void ProcessPackets() + { + foreach (var peer in _netPeers) + { + NetIncomingMessage msg; + while ((msg = peer.ReadMessage()) != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.VerboseDebugMessage: + Logger.DebugS("net", "{0}: {1}", peer.Configuration.LocalAddress, msg.ReadString()); + break; + + case NetIncomingMessageType.DebugMessage: + Logger.InfoS("net", "{0}: {1}", peer.Configuration.LocalAddress, msg.ReadString()); + break; + + case NetIncomingMessageType.WarningMessage: + Logger.WarningS("net", "{0}: {1}", peer.Configuration.LocalAddress, msg.ReadString()); + break; + + case NetIncomingMessageType.ErrorMessage: + Logger.ErrorS("net", "{0}: {1}", peer.Configuration.LocalAddress, msg.ReadString()); + break; + + case NetIncomingMessageType.ConnectionApproval: + HandleApproval(msg); + break; + + case NetIncomingMessageType.Data: + DispatchNetMessage(msg); + break; + + case NetIncomingMessageType.StatusChanged: + HandleStatusChanged(msg); + break; + + default: + Logger.WarningS("net", + "{0}: Unhandled incoming packet type from {1}: {2}", + peer.Configuration.LocalAddress, + msg.SenderConnection.RemoteEndPoint, + msg.MessageType); + break; + } + + peer.Recycle(msg); + } + } + } + + /// + public void ClientConnect(string host, int port, string userNameRequest) + { + DebugTools.Assert(!IsServer, "Should never be called on the server."); + DebugTools.Assert(!IsConnected); + + if (IsRunning) + { + ClientDisconnect("Client left server."); + } + + // Set up NetPeer. + var endPoint = NetUtility.Resolve(host, port); + + Logger.InfoS("net", $"Connecting to {endPoint}..."); + + var config = _getBaseNetPeerConfig(); + if (endPoint.AddressFamily == AddressFamily.InterNetworkV6) + { + config.LocalAddress = IPAddress.IPv6Any; + } + else + { + config.LocalAddress = IPAddress.Any; + } + + var peer = new NetPeer(config); + peer.Start(); + _netPeers.Add(peer); + var hail = peer.CreateMessage(); + hail.Write(userNameRequest); + peer.Connect(host, port, hail); + } + + /// + public void ClientDisconnect(string reason) + { + DebugTools.Assert(IsClient, "Should never be called on the server."); + Shutdown(reason); + } + + private NetPeerConfiguration _getBaseNetPeerConfig() { var netConfig = new NetPeerConfiguration("SS14_NetTag"); - if (IsServer) - { - netConfig.Port = Port; - netConfig.EnableMessageType(NetIncomingMessageType.ConnectionApproval); - } - #if DEBUG //Simulate Latency if (_config.GetCVar("net.fakelag")) @@ -134,140 +330,9 @@ namespace SS14.Shared.Network netConfig.ConnectionTimeout = 30000f; #endif - _netPeer = new NetPeer(netConfig); - - _netPeer.Start(); + return netConfig; } - public void Dispose() - { - if (_netPeer != null) - { - Shutdown("Network manager getting disposed."); - } - } - - /// - public void Shutdown(string reason) - { - foreach (var kvChannel in _channels) - DisconnectChannel(kvChannel.Value, reason); - - // request shutdown of the netPeer - _netPeer.Shutdown(reason); - - // wait for the network thread to finish its work (like flushing packets and gracefully disconnecting) - // Lidgren does not expose the thread, so we can't join or or anything - // pretty much have to poll every so often and wait for it to finish before continuing - // when the network thread is finished, it will change status from ShutdownRequested to NotRunning - while (_netPeer.Status == NetPeerStatus.ShutdownRequested) - { - // sleep the thread for an arbitrary length so it isn't spinning in the while loop as much - Thread.Sleep(50); - } - - _strings.Reset(); - } - - /// - public void Restart(string reason) - { - Shutdown(reason); - Startup(); - } - - /// - /// Process incoming packets. - /// - public void ProcessPackets() - { - // client does not always have its networking running, for example on main menu - if (IsClient && _netPeer == null) - return; - - // server on the other hand needs it to be running - DebugTools.Assert(_netPeer != null); - - NetIncomingMessage msg; - while ((msg = _netPeer.ReadMessage()) != null) - { - switch (msg.MessageType) - { - case NetIncomingMessageType.VerboseDebugMessage: - Logger.DebugS("net", msg.ReadString()); - break; - - case NetIncomingMessageType.DebugMessage: - Logger.InfoS("net", msg.ReadString()); - break; - - case NetIncomingMessageType.WarningMessage: - Logger.WarningS("net", msg.ReadString()); - break; - - case NetIncomingMessageType.ErrorMessage: - Logger.ErrorS("net", msg.ReadString()); - break; - - case NetIncomingMessageType.ConnectionApproval: - HandleApproval(msg); - break; - - case NetIncomingMessageType.Data: - DispatchNetMessage(msg); - break; - - case NetIncomingMessageType.StatusChanged: - HandleStatusChanged(msg); - break; - - default: - Logger.WarningS("net", "{msg.SenderConnection.RemoteEndPoint.Address}: Unhandled incoming packet type: {msg.MessageType}"); - break; - } - _netPeer.Recycle(msg); - } - } - - /// - public void ClientConnect(string host, int port, string userNameRequest) - { - DebugTools.Assert(_netPeer != null); - DebugTools.Assert(!IsServer, "Should never be called on the server."); - DebugTools.Assert(!IsConnected); - - if (_netPeer.ConnectionsCount > 0) - ClientDisconnect("Client left server."); - - Logger.InfoS("net", $"Connecting to {host}:{port}..."); - - var hail = _netPeer.CreateMessage(); - hail.Write(userNameRequest); - _netPeer.Connect(host, port, hail); - } - - /// - public void ClientDisconnect(string reason) - { - DebugTools.Assert(IsClient, "Should never be called on the server."); - - if (_netPeer == null) - return; - - // Client should never have more than one connection. - DebugTools.Assert(_netPeer.ConnectionsCount <= 1); - - foreach (var connection in _netPeer.Connections) - { - connection.Disconnect(reason); - } - - Shutdown(reason); - } - - /// - public INetChannel ServerChannel => _netPeer.ConnectionsCount > 0 ? GetChannel(_netPeer.Connections[0]) : null; - /// /// Gets the NetChannel of a peer NetConnection. /// @@ -298,11 +363,13 @@ namespace SS14.Shared.Network case NetConnectionStatus.Disconnected: if (_channels.ContainsKey(sender)) HandleDisconnect(msg); - else if (sender.RemoteUniqueIdentifier == 0) // is this the best way to detect an unsuccessful connect? + else if (sender.RemoteUniqueIdentifier == 0 + ) // is this the best way to detect an unsuccessful connect? { Logger.InfoS("net", $"{sender.RemoteEndPoint}: Failed to connect"); OnConnectFailed(); } + break; } } @@ -331,7 +398,7 @@ namespace SS14.Shared.Network if (OnConnecting(ip, session)) { _assignedSessions.Add(sender, session); - var msg = _netPeer.CreateMessage(); + var msg = message.SenderConnection.Peer.CreateMessage(); msg.Write(name); sender.Approve(msg); } @@ -352,6 +419,7 @@ namespace SS14.Shared.Network { session = _assignedSessions[sender]; } + var channel = new NetChannel(this, sender, session); _channels.Add(sender, channel); @@ -398,10 +466,11 @@ namespace SS14.Shared.Network private void DispatchNetMessage(NetIncomingMessage msg) { - if (_netPeer.Status == NetPeerStatus.ShutdownRequested) + var peer = msg.SenderConnection.Peer; + if (peer.Status == NetPeerStatus.ShutdownRequested) return; - if (_netPeer.Status == NetPeerStatus.NotRunning) + if (peer.Status == NetPeerStatus.NotRunning) return; if (!IsConnected) @@ -428,7 +497,7 @@ namespace SS14.Shared.Network } var channel = GetChannel(msg.SenderConnection); - var instance = (NetMessage)Activator.CreateInstance(packetType, channel); + var instance = (NetMessage) Activator.CreateInstance(packetType, channel); instance.MsgChannel = channel; try @@ -437,12 +506,14 @@ namespace SS14.Shared.Network } catch (Exception e) // yes, we want to catch ALL exeptions for security { - Logger.WarningS("net", $"{msg.SenderConnection.RemoteEndPoint}: Failed to deserialize {packetType.Name} packet: {e.Message}"); + Logger.WarningS("net", + $"{msg.SenderConnection.RemoteEndPoint}: Failed to deserialize {packetType.Name} packet: {e.Message}"); } if (!_callbacks.TryGetValue(packetType, out ProcessMessage callback)) { - Logger.WarningS("net", $"{msg.SenderConnection.RemoteEndPoint}: Received packet {id}:{name}, but callback was not registered."); + Logger.WarningS("net", + $"{msg.SenderConnection.RemoteEndPoint}: Received packet {id}:{name}, but callback was not registered."); return; } @@ -460,24 +531,25 @@ namespace SS14.Shared.Network _messages.Add(name, typeof(T)); if (rxCallback != null) - _callbacks.Add(typeof(T), msg => rxCallback((T)msg)); + _callbacks.Add(typeof(T), msg => rxCallback((T) msg)); } /// public T CreateNetMessage() where T : NetMessage { - return (T)Activator.CreateInstance(typeof(T), (INetChannel)null); + return (T) Activator.CreateInstance(typeof(T), (INetChannel) null); } - private NetOutgoingMessage BuildMessage(NetMessage message) + private NetOutgoingMessage BuildMessage(NetMessage message, NetPeer peer) { - var packet = _netPeer.CreateMessage(4); + var packet = peer.CreateMessage(4); if (!_strings.TryFindStringId(message.MsgName, out int msgId)) - throw new NetManagerException($"[NET] No string in table with name {message.MsgName}. Was it registered?"); + throw new NetManagerException( + $"[NET] No string in table with name {message.MsgName}. Was it registered?"); - packet.Write((byte)msgId); + packet.Write((byte) msgId); message.WriteToBuffer(packet); return packet; } @@ -485,31 +557,40 @@ namespace SS14.Shared.Network /// public void ServerSendToAll(NetMessage message) { - if (_netPeer == null || _netPeer.ConnectionsCount == 0) + DebugTools.Assert(IsServer); + + if (!IsConnected) return; - var packet = BuildMessage(message); - var method = GetMethod(message.MsgGroup); - _netPeer.SendMessage(packet, _netPeer.Connections, method, 0); + foreach (var peer in _netPeers) + { + var packet = BuildMessage(message, peer); + var method = GetMethod(message.MsgGroup); + if (peer.ConnectionsCount == 0) + { + continue; + } + peer.SendMessage(packet, peer.Connections, method, 0); + } } /// public void ServerSendMessage(NetMessage message, INetChannel recipient) { - if (_netPeer == null) - return; - + DebugTools.Assert(IsServer); if (!(recipient is NetChannel channel)) throw new ArgumentException($"Not of type {typeof(NetChannel).FullName}", nameof(recipient)); - var packet = BuildMessage(message); - _netPeer.SendMessage(packet, channel.Connection, NetDeliveryMethod.ReliableOrdered); + var peer = channel.Connection.Peer; + var packet = BuildMessage(message, peer); + peer.SendMessage(packet, channel.Connection, NetDeliveryMethod.ReliableOrdered); } /// public void ServerSendToMany(NetMessage message, List recipients) { - if (_netPeer == null) + DebugTools.Assert(IsServer); + if (!IsConnected) return; foreach (var channel in recipients) @@ -521,16 +602,19 @@ namespace SS14.Shared.Network /// public void ClientSendMessage(NetMessage message) { - if (_netPeer == null) - return; + DebugTools.Assert(IsClient); // not connected to a server, so a message cannot be sent to it. if (!IsConnected) return; - var packet = BuildMessage(message); + DebugTools.Assert(_netPeers.Count == 1); + DebugTools.Assert(_netPeers[0].ConnectionsCount == 1); + + var peer = _netPeers[0]; + var packet = BuildMessage(message, peer); var method = GetMethod(message.MsgGroup); - _netPeer.SendMessage(packet, _netPeer.Connections[0], method); + peer.SendMessage(packet, peer.Connections[0], method); } #endregion NetMessages @@ -626,6 +710,14 @@ namespace SS14.Shared.Network /// public readonly int ReceivedPackets; + public NetworkStats(int sentBytes, int receivedBytes, int sentPackets, int receivedPackets) + { + SentBytes = sentBytes; + ReceivedBytes = receivedBytes; + SentPackets = sentPackets; + ReceivedPackets = receivedPackets; + } + /// /// Creates an instance of this object. ///