Implement IPv6 support for server and direct connect

This commit is contained in:
jrb0001
2020-01-06 22:19:52 +01:00
committed by reaperrr
parent bd1a936c7a
commit bf397591f9
16 changed files with 328 additions and 118 deletions

View File

@@ -10,8 +10,11 @@
#endregion
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using OpenRA.Server;
@@ -30,12 +33,63 @@ namespace OpenRA.Network
{
int LocalClientId { get; }
ConnectionState ConnectionState { get; }
IPEndPoint EndPoint { get; }
string ErrorMessage { get; }
void Send(int frame, List<byte[]> orders);
void SendImmediate(IEnumerable<byte[]> orders);
void SendSync(int frame, byte[] syncData);
void Receive(Action<int, byte[]> packetFn);
}
public class ConnectionTarget
{
readonly DnsEndPoint[] endpoints;
public ConnectionTarget()
{
endpoints = new[] { new DnsEndPoint("invalid", 0) };
}
public ConnectionTarget(string host, int port)
{
endpoints = new[] { new DnsEndPoint(host, port) };
}
public ConnectionTarget(IEnumerable<DnsEndPoint> endpoints)
{
this.endpoints = endpoints.ToArray();
if (this.endpoints.Length == 0)
{
throw new ArgumentException("ConnectionTarget must have at least one address.");
}
}
public IEnumerable<IPEndPoint> GetConnectEndPoints()
{
return endpoints
.SelectMany(e =>
{
try
{
return Dns.GetHostAddresses(e.Host)
.Select(a => new IPEndPoint(a, e.Port));
}
catch (Exception)
{
return Enumerable.Empty<IPEndPoint>();
}
})
.ToList();
}
public override string ToString()
{
return endpoints
.Select(e => "{0}:{1}".F(e.Host, e.Port))
.JoinWith("/");
}
}
class EchoConnection : IConnection
{
protected struct ReceivedPacket
@@ -57,6 +111,16 @@ namespace OpenRA.Network
get { return ConnectionState.PreConnecting; }
}
public virtual IPEndPoint EndPoint
{
get { throw new NotSupportedException("An echo connection doesn't have an endpoint"); }
}
public virtual string ErrorMessage
{
get { return null; }
}
public virtual void Send(int frame, List<byte[]> orders)
{
var ms = new MemoryStream();
@@ -138,35 +202,100 @@ namespace OpenRA.Network
sealed class NetworkConnection : EchoConnection
{
readonly TcpClient tcp;
readonly ConnectionTarget target;
TcpClient tcp;
IPEndPoint endpoint;
readonly List<byte[]> queuedSyncPackets = new List<byte[]>();
volatile ConnectionState connectionState = ConnectionState.Connecting;
volatile int clientId;
bool disposed;
string errorMessage;
public NetworkConnection(string host, int port)
public override IPEndPoint EndPoint { get { return endpoint; } }
public override string ErrorMessage { get { return errorMessage; } }
public NetworkConnection(ConnectionTarget target)
{
try
this.target = target;
new Thread(NetworkConnectionConnect)
{
tcp = new TcpClient(host, port) { NoDelay = true };
Name = "{0} (connect to {1})".F(GetType().Name, target),
IsBackground = true
}.Start();
}
void NetworkConnectionConnect()
{
var queue = new BlockingCollection<TcpClient>();
var atLeastOneEndpoint = false;
foreach (var endpoint in target.GetConnectEndPoints())
{
atLeastOneEndpoint = true;
new Thread(() =>
{
try
{
var client = new TcpClient(endpoint.AddressFamily) { NoDelay = true };
client.Connect(endpoint.Address, endpoint.Port);
try
{
queue.Add(client);
}
catch (InvalidOperationException)
{
// Another connection was faster, close this one.
client.Close();
}
}
catch (Exception ex)
{
errorMessage = "Failed to connect to {0}".F(endpoint);
Log.Write("client", "Failed to connect to {0}: {1}".F(endpoint, ex.Message));
}
})
{
Name = "{0} (connect to {1})".F(GetType().Name, endpoint),
IsBackground = true
}.Start();
}
if (!atLeastOneEndpoint)
{
errorMessage = "Failed to resolve addresses for {0}".F(target);
connectionState = ConnectionState.NotConnected;
}
// Wait up to 5s for a successful connection. This should hopefully be enough because such high latency makes the game unplayable anyway.
else if (queue.TryTake(out tcp, 5000))
{
// Copy endpoint here to have it even after getting disconnected.
endpoint = (IPEndPoint)tcp.Client.RemoteEndPoint;
new Thread(NetworkConnectionReceive)
{
Name = GetType().Name + " " + host + ":" + port,
Name = "{0} (receive from {1})".F(GetType().Name, tcp.Client.RemoteEndPoint),
IsBackground = true
}.Start(tcp.GetStream());
}.Start();
}
catch
else
{
connectionState = ConnectionState.NotConnected;
}
// Close all unneeded connections in the queue and make sure new ones are closed on the connect thread.
queue.CompleteAdding();
foreach (var client in queue)
client.Close();
}
void NetworkConnectionReceive(object networkStreamObject)
void NetworkConnectionReceive()
{
try
{
var networkStream = (NetworkStream)networkStreamObject;
var reader = new BinaryReader(networkStream);
var reader = new BinaryReader(tcp.GetStream());
var handshakeProtocol = reader.ReadInt32();
if (handshakeProtocol != ProtocolVersion.Handshake)
@@ -187,7 +316,11 @@ namespace OpenRA.Network
AddPacket(new ReceivedPacket { FromClient = client, Data = buf });
}
}
catch { }
catch (Exception ex)
{
errorMessage = "Connection to {0} failed".F(endpoint);
Log.Write("client", "Connection to {0} failed: {1}".F(endpoint, ex.Message));
}
finally
{
connectionState = ConnectionState.NotConnected;

View File

@@ -28,11 +28,10 @@ namespace OpenRA.Network
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
public World World;
public readonly string Host;
public readonly int Port;
public readonly ConnectionTarget Endpoint;
public readonly string Password = "";
public string ServerError = "Server is not responding";
public string ServerError = null;
public bool AuthenticationFailed = false;
public ExternalMod ServerExternalMod = null;
@@ -80,10 +79,9 @@ namespace OpenRA.Network
Connection.Send(i, new List<byte[]>());
}
public OrderManager(string host, int port, string password, IConnection conn)
public OrderManager(ConnectionTarget endpoint, string password, IConnection conn)
{
Host = host;
Port = port;
Endpoint = endpoint;
Password = password;
Connection = conn;
syncReport = new SyncReport(this);

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using OpenRA.FileFormats;
using OpenRA.Primitives;
@@ -32,6 +33,13 @@ namespace OpenRA.Network
public int LocalClientId { get { return -1; } }
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
public IPEndPoint EndPoint
{
get { throw new NotSupportedException("A replay connection doesn't have an endpoint"); }
}
public string ErrorMessage { get { return null; } }
public readonly int TickCount;
public readonly int FinalGameTick;
public readonly bool IsValid;