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