Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 86 additions & 57 deletions DarkRift.Client/BichannelClientConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public bool NoDelay {
/// </summary>
public bool PreserveTcpOrdering { get; private set; } = true;

/// <summary>
/// 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.
/// </summary>
public bool TcpOnly { get; private set; } = true;

/// <inheritdoc/>
public override IEnumerable<IPEndPoint> RemoteEndPoints => new IPEndPoint[] { RemoteTcpEndPoint, RemoteUdpEndPoint };

Expand All @@ -65,6 +72,11 @@ public bool NoDelay {
/// </summary>
private readonly Socket udpSocket;

/// <summary>
/// Are we even using UDP?
/// </summary>
private bool EnableUdp => !TcpOnly;

/// <summary>
/// Creates a new bichannel client.
/// </summary>
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down Expand Up @@ -313,6 +336,11 @@ public override bool SendMessageReliable(MessageBuffer message)
/// <inheritdoc/>
public override bool SendMessageUnreliable(MessageBuffer message)
{
if (TcpOnly)
{
return SendMessageReliable(message);
}

if (connectionState == ConnectionState.Disconnected)
{
message.Dispose();
Expand Down Expand Up @@ -794,7 +822,8 @@ protected override void Dispose(bool disposing)
Disconnect();

tcpSocket.Close();
udpSocket.Close();
if (EnableUdp)
udpSocket.Close();
}

disposedValue = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ public abstract class AbstractBichannelListener : NetworkListener
/// </summary>
public abstract bool PreserveTcpOrdering { get; protected set; }

/// <summary>
/// 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.
/// </summary>
public abstract bool TcpOnly { get; protected set; }

/// <summary>
/// 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.
Expand Down
25 changes: 16 additions & 9 deletions DarkRift.Server/Plugins/Listeners/Bichannel/BichannelListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 : "")}.");
}
Expand Down
Loading