From 0bbf074b085d2230f18fe7611658f65ef54e607b Mon Sep 17 00:00:00 2001 From: "petter.hansson" Date: Fri, 23 Dec 2022 18:41:25 +0100 Subject: [PATCH] TCP only feature, WIP. Currently does not have tcpOnly settings for listener and client and defaults to true (whereas it's intended to default to false) due to testing. Testing however is proving difficult because it uses fewer recycled objects which many tests trigger on. --- DarkRift.Client/BichannelClientConnection.cs | 143 +++++++++++------- .../Bichannel/AbstractBichannelListener.cs | 7 + .../Listeners/Bichannel/BichannelListener.cs | 25 +-- .../Bichannel/BichannelListenerBase.cs | 96 ++++++++---- .../Bichannel/BichannelServerConnection.cs | 5 + .../CompatibilityBichannelListener.cs | 5 + 6 files changed, 186 insertions(+), 95 deletions(-) diff --git a/DarkRift.Client/BichannelClientConnection.cs b/DarkRift.Client/BichannelClientConnection.cs index a99ddf7..07fa470 100644 --- a/DarkRift.Client/BichannelClientConnection.cs +++ b/DarkRift.Client/BichannelClientConnection.cs @@ -44,6 +44,13 @@ public bool NoDelay { /// public bool PreserveTcpOrdering { get; private set; } = true; + /// + /// If set to true, all messages (whether marked as reliable or unreliable) are sent over TCP. + /// This modifies the connection sequence which means this client will only + /// be able to connect to DR2 server bichannel listeners marked as TcpOnly. + /// + public bool TcpOnly { get; private set; } = true; + /// public override IEnumerable RemoteEndPoints => new IPEndPoint[] { RemoteTcpEndPoint, RemoteUdpEndPoint }; @@ -65,6 +72,11 @@ public bool NoDelay { /// private readonly Socket udpSocket; + /// + /// Are we even using UDP? + /// + private bool EnableUdp => !TcpOnly; + /// /// Creates a new bichannel client. /// @@ -87,10 +99,12 @@ public BichannelClientConnection(IPAddress ipAddress, int tcpPort, int udpPort, : base () { RemoteTcpEndPoint = new IPEndPoint(ipAddress, tcpPort); - RemoteUdpEndPoint = new IPEndPoint(ipAddress, udpPort); + if (EnableUdp) + RemoteUdpEndPoint = new IPEndPoint(ipAddress, udpPort); tcpSocket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - udpSocket = new Socket(tcpSocket.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + if (EnableUdp) + udpSocket = new Socket(tcpSocket.AddressFamily, SocketType.Dgram, ProtocolType.Udp); NoDelay = noDelay; } @@ -134,15 +148,18 @@ public override void Connect() throw new DarkRiftConnectionException("Unable to establish TCP connection to remote server.", e); } - try - { - //Bind UDP to a free port - udpSocket.Bind(new IPEndPoint(((IPEndPoint)tcpSocket.LocalEndPoint).Address, 0)); - udpSocket.Connect(RemoteUdpEndPoint); - } - catch (SocketException e) + if (EnableUdp) { - throw new DarkRiftConnectionException("Unable to bind UDP ports.", e); + try + { + //Bind UDP to a free port + udpSocket.Bind(new IPEndPoint(((IPEndPoint)tcpSocket.LocalEndPoint).Address, 0)); + udpSocket.Connect(RemoteUdpEndPoint); + } + catch (SocketException e) + { + throw new DarkRiftConnectionException("Unable to bind UDP ports.", e); + } } //Receive auth token from TCP @@ -179,56 +196,59 @@ public override void Connect() throw new DarkRiftConnectionException(errorMessage, SocketError.ConnectionAborted); } - //Transmit token back over UDP to server listening port - udpSocket.Send(buffer); + if (EnableUdp) + { + //Transmit token back over UDP to server listening port + udpSocket.Send(buffer); - //Receive response from server to initiate the connection - int udpAcknowledgmentSize = protocolVersion >= 1 ? 8 : 1; - byte[] udpBuffer = new byte[udpAcknowledgmentSize]; - udpSocket.ReceiveTimeout = 5000; + //Receive response from server to initiate the connection + int udpAcknowledgmentSize = protocolVersion >= 1 ? 8 : 1; + byte[] udpBuffer = new byte[udpAcknowledgmentSize]; + udpSocket.ReceiveTimeout = 5000; - int receivedUdp; - try - { - receivedUdp = udpSocket.Receive(udpBuffer); - } - catch (SocketException ex) - { - throw new DarkRiftConnectionException("UDP acknowledgment reception error", ex); - } - finally - { - udpSocket.ReceiveTimeout = 0; //Reset to infinite - } + int receivedUdp; + try + { + receivedUdp = udpSocket.Receive(udpBuffer); + } + catch (SocketException ex) + { + throw new DarkRiftConnectionException("UDP acknowledgment reception error", ex); + } + finally + { + udpSocket.ReceiveTimeout = 0; //Reset to infinite + } - bool failedUdpReceive = receivedUdp != udpAcknowledgmentSize; - if (!failedUdpReceive) - { - if (protocolVersion != 0) + bool failedUdpReceive = receivedUdp != udpAcknowledgmentSize; + if (!failedUdpReceive) { - for (int i = 0; i < udpAcknowledgmentSize; ++i) + if (protocolVersion != 0) { - if (udpBuffer[i] != buffer[i + 1]) + for (int i = 0; i < udpAcknowledgmentSize; ++i) { - failedUdpReceive = true; - break; + if (udpBuffer[i] != buffer[i + 1]) + { + failedUdpReceive = true; + break; + } } } + if (protocolVersion == 0) + { + if (udpBuffer[0] != 0) + failedUdpReceive = true; + } } - if (protocolVersion == 0) + + if (failedUdpReceive) { - if (udpBuffer[0] != 0) - failedUdpReceive = true; + tcpSocket.Shutdown(SocketShutdown.Both); + string errorMessage = receivedUdp == 0 ? "Timeout waiting for UDP acknowledgment from server." + : "Malformatted UDP acknowledgement from server."; + throw new DarkRiftConnectionException(errorMessage, SocketError.ConnectionAborted); } } - - if (failedUdpReceive) - { - tcpSocket.Shutdown(SocketShutdown.Both); - string errorMessage = receivedUdp == 0 ? "Timeout waiting for UDP acknowledgment from server." - : "Malformatted UDP acknowledgement from server."; - throw new DarkRiftConnectionException(errorMessage, SocketError.ConnectionAborted); - } } catch (DarkRiftConnectionException) { @@ -252,16 +272,19 @@ public override void Connect() if (!headerCompletingAsync) AsyncReceiveHeaderCompleted(this, tcpArgs); - //Start receiving UDP packets - SocketAsyncEventArgs udpArgs = ObjectCache.GetSocketAsyncEventArgs(); - udpArgs.BufferList = null; - udpArgs.SetBuffer(new byte[ushort.MaxValue], 0, ushort.MaxValue); + if (EnableUdp) + { + //Start receiving UDP packets + SocketAsyncEventArgs udpArgs = ObjectCache.GetSocketAsyncEventArgs(); + udpArgs.BufferList = null; + udpArgs.SetBuffer(new byte[ushort.MaxValue], 0, ushort.MaxValue); - udpArgs.Completed += UdpReceiveCompleted; + udpArgs.Completed += UdpReceiveCompleted; - bool udpCompletingAsync = udpSocket.ReceiveAsync(udpArgs); - if (!udpCompletingAsync) - UdpReceiveCompleted(this, udpArgs); + bool udpCompletingAsync = udpSocket.ReceiveAsync(udpArgs); + if (!udpCompletingAsync) + UdpReceiveCompleted(this, udpArgs); + } //Mark connected to allow sending connectionState = ConnectionState.Connected; @@ -313,6 +336,11 @@ public override bool SendMessageReliable(MessageBuffer message) /// public override bool SendMessageUnreliable(MessageBuffer message) { + if (TcpOnly) + { + return SendMessageReliable(message); + } + if (connectionState == ConnectionState.Disconnected) { message.Dispose(); @@ -794,7 +822,8 @@ protected override void Dispose(bool disposing) Disconnect(); tcpSocket.Close(); - udpSocket.Close(); + if (EnableUdp) + udpSocket.Close(); } disposedValue = true; diff --git a/DarkRift.Server/Plugins/Listeners/Bichannel/AbstractBichannelListener.cs b/DarkRift.Server/Plugins/Listeners/Bichannel/AbstractBichannelListener.cs index 41035e2..dd1c32b 100644 --- a/DarkRift.Server/Plugins/Listeners/Bichannel/AbstractBichannelListener.cs +++ b/DarkRift.Server/Plugins/Listeners/Bichannel/AbstractBichannelListener.cs @@ -29,6 +29,13 @@ public abstract class AbstractBichannelListener : NetworkListener /// public abstract bool PreserveTcpOrdering { get; protected set; } + /// + /// If set to true, all messages (whether marked as reliable or unreliable) are sent over TCP. + /// This modifies the connection sequence which means this server will only + /// be able to serve to DR2 clients marked as TcpOnly. + /// + public abstract bool TcpOnly { get; protected set; } + /// /// The version of the protocol used. The defaults to the latest version. /// You only need to change this if you intend to retain backwards compatibility. diff --git a/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListener.cs b/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListener.cs index fa8f949..eca01f8 100644 --- a/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListener.cs +++ b/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListener.cs @@ -45,17 +45,24 @@ public override void StartListening() if (!completingAsync) TcpAcceptCompleted(this, tcpArgs); - //Sort UDP - SocketAsyncEventArgs udpArgs = ObjectCache.GetSocketAsyncEventArgs(); - udpArgs.Completed += UdpMessageReceived; + if (EnableUdp) + { + //Sort UDP + SocketAsyncEventArgs udpArgs = ObjectCache.GetSocketAsyncEventArgs(); + udpArgs.Completed += UdpMessageReceived; - udpArgs.RemoteEndPoint = new IPEndPoint(Address, 0); - udpArgs.BufferList = null; - udpArgs.SetBuffer(new byte[ushort.MaxValue], 0, ushort.MaxValue); + udpArgs.RemoteEndPoint = new IPEndPoint(Address, 0); + udpArgs.BufferList = null; + udpArgs.SetBuffer(new byte[ushort.MaxValue], 0, ushort.MaxValue); - completingAsync = UdpListener.ReceiveFromAsync(udpArgs); - if (!completingAsync) - UdpMessageReceived(this, udpArgs); + completingAsync = UdpListener.ReceiveFromAsync(udpArgs); + if (!completingAsync) + UdpMessageReceived(this, udpArgs); + } + else + { + UdpPort = Port; // Cleaner message. + } Logger.Info($"Server mounted, listening on port {Port}{(UdpPort != Port ? "|" + UdpPort : "")}."); } diff --git a/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListenerBase.cs b/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListenerBase.cs index fe42228..653c5f6 100644 --- a/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListenerBase.cs +++ b/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListenerBase.cs @@ -50,6 +50,13 @@ public override bool NoDelay { /// public override bool PreserveTcpOrdering { get; protected set; } = true; + /// + /// If set to true, all messages (whether marked as reliable or unreliable) are sent over TCP. + /// This modifies the connection sequence which means this server will only + /// be able to serve to DR2 clients marked as TcpOnly. + /// + public override bool TcpOnly { get; protected set; } = true; + /// /// The version of the protocol used. The defaults to the latest version. /// You only need to change this if you intend to retain backwards compatibility. @@ -94,6 +101,11 @@ protected struct PendingConnection /// private readonly ICounterMetric connectionAttemptTimeoutsCounter; + /// + /// Are we even using UDP? + /// + protected bool EnableUdp => !TcpOnly; + public BichannelListenerBase(NetworkListenerLoadData listenerLoadData) : base(listenerLoadData) { @@ -117,7 +129,8 @@ public BichannelListenerBase(NetworkListenerLoadData listenerLoadData) this.PreserveTcpOrdering = preserveTcpOrdering == null || preserveTcpOrdering == "true"; // keep this true by default, but if user specifies it in config they probably want to disable it TcpListener = new Socket(Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - UdpListener = new Socket(Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + if (EnableUdp) + UdpListener = new Socket(Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); // TODO DR3 this should default to true this.NoDelay = listenerLoadData.Settings["noDelay"]?.ToLower() == "true"; @@ -127,15 +140,18 @@ public BichannelListenerBase(NetworkListenerLoadData listenerLoadData) else this.MaxTcpBodyLength = 65535; - // By default on Windows ICMP Port Unreachable messages cause the socket to close, we really don't want that - // https://stackoverflow.com/a/74327430/2755790 - try - { - UdpListener.IOControl(SIO_UDP_CONNRESET, new byte[] { 0x00 }, null); - } - catch (PlatformNotSupportedException) + if (EnableUdp) { - // Not on Windows, no need to worry about the option + // By default on Windows ICMP Port Unreachable messages cause the socket to close, we really don't want that + // https://stackoverflow.com/a/74327430/2755790 + try + { + UdpListener.IOControl(SIO_UDP_CONNRESET, new byte[] { 0x00 }, null); + } + catch (PlatformNotSupportedException) + { + // Not on Windows, no need to worry about the option + } } connectionAttemptTimeoutsCounter = MetricsCollector.Counter("connection_attempt_timeouts", "The number of connection attempts made to this listener that timed out."); @@ -152,10 +168,13 @@ protected void BindSockets() // If Port was set as 0, we'll now have been assigned a port. Use that from now on Port = (ushort)((IPEndPoint)TcpListener.LocalEndPoint).Port; - UdpListener.Bind(new IPEndPoint(Address, UdpPort)); + if (EnableUdp) + { + UdpListener.Bind(new IPEndPoint(Address, UdpPort)); - // If UdpPort was set as 0, we'll now have been assigned a port. Use that from now on - UdpPort = (ushort)((IPEndPoint)UdpListener.LocalEndPoint).Port; + // If UdpPort was set as 0, we'll now have been assigned a port. Use that from now on + UdpPort = (ushort)((IPEndPoint)UdpListener.LocalEndPoint).Port; + } } /// @@ -198,6 +217,18 @@ protected void HandleTcpConnection(Socket acceptSocket) buffer[0] = (byte)BichannelProtocolVersion; BigEndianHelper.WriteBytes(buffer, 1, token); acceptSocket.Send(buffer); + + if (TcpOnly) + { + using (MessageBuffer evenBetterBuffer = MessageBuffer.Create(buffer.Length)) + { + evenBetterBuffer.Count = buffer.Length; + for (int i = 0; i < buffer.Length; ++i) + evenBetterBuffer.Buffer[i] = buffer[i]; + + HandleUdpConnection(evenBetterBuffer, acceptSocket.RemoteEndPoint); + } + } } catch (SocketException e) { @@ -272,6 +303,8 @@ private EndPoint CancelPendingTcpConnection(long token) /// The originating endpoint. protected void HandleUdpConnection(MessageBuffer buffer, EndPoint remoteEndPoint) { + //Note: This function can be called by despite TcpOnly/!EnableUdp. + //Check length if (buffer.Count != 9) return; @@ -300,7 +333,8 @@ protected void HandleUdpConnection(MessageBuffer buffer, EndPoint remoteEndPoint if (tcpSocket != null) { - Logger.Trace("Accepted UDP connection from " + remoteEndPoint + "."); + if (EnableUdp) + Logger.Trace("Accepted UDP connection from " + remoteEndPoint + "."); //Create connection object BichannelServerConnection connection = new BichannelServerConnection( @@ -311,23 +345,26 @@ protected void HandleUdpConnection(MessageBuffer buffer, EndPoint remoteEndPoint MetricsManager.GetPerMessageMetricsCollectorFor(Name) ); - // Send message back to client to say hi - // This MemoryBuffer is not supposed to be disposed here! It's disposed when the message is sent! - int numBytesOfHello = BichannelProtocolVersion >= 1? 8 : 1; // This used to be 1 which caused issues with some ISPs. - MessageBuffer helloBuffer = MessageBuffer.Create(numBytesOfHello); - helloBuffer.Count = numBytesOfHello; - - if (BichannelProtocolVersion >= 1) + if (EnableUdp) { - for (int i = 0; i < numBytesOfHello; ++i) - helloBuffer.Buffer[i] = buffer.Buffer[i + 1]; - } - else - { - helloBuffer.Buffer[0] = 0; + // Send message back to client to say hi + // This MemoryBuffer is not supposed to be disposed here! It's disposed when the message is sent! + int numBytesOfHello = BichannelProtocolVersion >= 1 ? 8 : 1; // This used to be 1 which caused issues with some ISPs. + MessageBuffer helloBuffer = MessageBuffer.Create(numBytesOfHello); + helloBuffer.Count = numBytesOfHello; + + if (BichannelProtocolVersion >= 1) + { + for (int i = 0; i < numBytesOfHello; ++i) + helloBuffer.Buffer[i] = buffer.Buffer[i + 1]; + } + else + { + helloBuffer.Buffer[0] = 0; + } + + connection.SendMessageUnreliable(helloBuffer); } - - connection.SendMessageUnreliable(helloBuffer); //Inform everyone RegisterConnection(connection); @@ -379,7 +416,8 @@ protected override void Dispose(bool disposing) if (disposing) { TcpListener.Close(); - UdpListener.Close(); + if (EnableUdp) + UdpListener.Close(); } disposedValue = true; diff --git a/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelServerConnection.cs b/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelServerConnection.cs index f5faa74..321bf9b 100644 --- a/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelServerConnection.cs +++ b/DarkRift.Server/Plugins/Listeners/Bichannel/BichannelServerConnection.cs @@ -178,6 +178,11 @@ public override bool SendMessageReliable(MessageBuffer message) /// public override bool SendMessageUnreliable(MessageBuffer message) { + if (networkListener.TcpOnly) + { + return SendMessageReliable(message); + } + if (!CanSend) { message.Dispose(); diff --git a/DarkRift.Server/Plugins/Listeners/Bichannel/CompatibilityBichannelListener.cs b/DarkRift.Server/Plugins/Listeners/Bichannel/CompatibilityBichannelListener.cs index 4c0da12..ded0d00 100644 --- a/DarkRift.Server/Plugins/Listeners/Bichannel/CompatibilityBichannelListener.cs +++ b/DarkRift.Server/Plugins/Listeners/Bichannel/CompatibilityBichannelListener.cs @@ -21,6 +21,11 @@ public CompatibilityBichannelListener(NetworkListenerLoadData listenerLoadData) public override void StartListening() { + if (TcpOnly) + { + throw new InvalidOperationException(nameof(CompatibilityBichannelListener) + " has no support for " + nameof(TcpOnly)); + } + BindSockets(); Logger.Trace("Starting compatibility listener.");