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.");