Skip to content

Commit bf39759

Browse files
jrb0001reaperrr
authored andcommitted
Implement IPv6 support for server and direct connect
1 parent bd1a936 commit bf39759

File tree

16 files changed

+328
-118
lines changed

16 files changed

+328
-118
lines changed

Diff for: OpenRA.Game/Game.cs

+28-15
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ public static class Game
6161

6262
public static event Action OnShellmapLoaded = () => { };
6363

64-
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
64+
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
6565
{
66-
var connection = new NetworkConnection(host, port);
66+
var connection = new NetworkConnection(endpoint);
6767
if (recordReplay)
6868
connection.StartRecording(() => { return TimestampedFilename(); });
6969

70-
var om = new OrderManager(host, port, password, connection);
70+
var om = new OrderManager(endpoint, password, connection);
7171
JoinInner(om);
7272
return om;
7373
}
@@ -88,12 +88,12 @@ static void JoinInner(OrderManager om)
8888

8989
public static void JoinReplay(string replayFile)
9090
{
91-
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
91+
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
9292
}
9393

9494
static void JoinLocal()
9595
{
96-
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
96+
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
9797
}
9898

9999
// More accurate replacement for Environment.TickCount
@@ -104,14 +104,14 @@ static void JoinLocal()
104104
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
105105
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
106106

107-
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
107+
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
108108
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
109109
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
110110
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
111111

112-
public static void RemoteDirectConnect(string host, int port)
112+
public static void RemoteDirectConnect(ConnectionTarget endpoint)
113113
{
114-
OnRemoteDirectConnect(host, port);
114+
OnRemoteDirectConnect(endpoint);
115115
}
116116

117117
// Hacky workaround for orderManager visibility
@@ -233,7 +233,7 @@ public static void CreateAndStartLocalServer(string mapUID, IEnumerable<Order> s
233233

234234
LobbyInfoChanged += lobbyReady;
235235

236-
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
236+
om = JoinServer(CreateLocalServer(mapUID), "");
237237
}
238238

239239
public static bool IsHost
@@ -301,6 +301,7 @@ static void Initialize(Arguments args)
301301
Log.AddChannel("graphics", "graphics.log");
302302
Log.AddChannel("geoip", "geoip.log");
303303
Log.AddChannel("nat", "nat.log");
304+
Log.AddChannel("client", "client.log");
304305

305306
var platforms = new[] { Settings.Game.Platform, "Default", null };
306307
foreach (var p in platforms)
@@ -384,7 +385,7 @@ public static void InitializeMod(string mod, Arguments args)
384385
LobbyInfoChanged = () => { };
385386
ConnectionStateChanged = om => { };
386387
BeforeGameStart = () => { };
387-
OnRemoteDirectConnect = (a, b) => { };
388+
OnRemoteDirectConnect = endpoint => { };
388389
delayedActions = new ActionQueue();
389390

390391
Ui.ResetAll();
@@ -898,12 +899,19 @@ public static T CreateObject<T>(string name)
898899
return ModData.ObjectCreator.CreateObject<T>(name);
899900
}
900901

901-
public static void CreateServer(ServerSettings settings)
902+
public static ConnectionTarget CreateServer(ServerSettings settings)
902903
{
903-
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
904+
var endpoints = new List<IPEndPoint>
905+
{
906+
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
907+
new IPEndPoint(IPAddress.Any, settings.ListenPort)
908+
};
909+
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
910+
911+
return server.GetEndpointForLocalConnection();
904912
}
905913

906-
public static int CreateLocalServer(string map)
914+
public static ConnectionTarget CreateLocalServer(string map)
907915
{
908916
var settings = new ServerSettings()
909917
{
@@ -912,9 +920,14 @@ public static int CreateLocalServer(string map)
912920
AdvertiseOnline = false
913921
};
914922

915-
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
923+
var endpoints = new List<IPEndPoint>
924+
{
925+
new IPEndPoint(IPAddress.IPv6Loopback, 0),
926+
new IPEndPoint(IPAddress.Loopback, 0)
927+
};
928+
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
916929

917-
return server.Port;
930+
return server.GetEndpointForLocalConnection();
918931
}
919932

920933
public static bool IsCurrentWorld(World world)

Diff for: OpenRA.Game/Network/Connection.cs

+144-11
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
#endregion
1111

1212
using System;
13+
using System.Collections.Concurrent;
1314
using System.Collections.Generic;
1415
using System.IO;
16+
using System.Linq;
17+
using System.Net;
1518
using System.Net.Sockets;
1619
using System.Threading;
1720
using OpenRA.Server;
@@ -30,12 +33,63 @@ public interface IConnection : IDisposable
3033
{
3134
int LocalClientId { get; }
3235
ConnectionState ConnectionState { get; }
36+
IPEndPoint EndPoint { get; }
37+
string ErrorMessage { get; }
3338
void Send(int frame, List<byte[]> orders);
3439
void SendImmediate(IEnumerable<byte[]> orders);
3540
void SendSync(int frame, byte[] syncData);
3641
void Receive(Action<int, byte[]> packetFn);
3742
}
3843

44+
public class ConnectionTarget
45+
{
46+
readonly DnsEndPoint[] endpoints;
47+
48+
public ConnectionTarget()
49+
{
50+
endpoints = new[] { new DnsEndPoint("invalid", 0) };
51+
}
52+
53+
public ConnectionTarget(string host, int port)
54+
{
55+
endpoints = new[] { new DnsEndPoint(host, port) };
56+
}
57+
58+
public ConnectionTarget(IEnumerable<DnsEndPoint> endpoints)
59+
{
60+
this.endpoints = endpoints.ToArray();
61+
if (this.endpoints.Length == 0)
62+
{
63+
throw new ArgumentException("ConnectionTarget must have at least one address.");
64+
}
65+
}
66+
67+
public IEnumerable<IPEndPoint> GetConnectEndPoints()
68+
{
69+
return endpoints
70+
.SelectMany(e =>
71+
{
72+
try
73+
{
74+
return Dns.GetHostAddresses(e.Host)
75+
.Select(a => new IPEndPoint(a, e.Port));
76+
}
77+
catch (Exception)
78+
{
79+
return Enumerable.Empty<IPEndPoint>();
80+
}
81+
})
82+
.ToList();
83+
}
84+
85+
public override string ToString()
86+
{
87+
return endpoints
88+
.Select(e => "{0}:{1}".F(e.Host, e.Port))
89+
.JoinWith("/");
90+
}
91+
}
92+
3993
class EchoConnection : IConnection
4094
{
4195
protected struct ReceivedPacket
@@ -57,6 +111,16 @@ public virtual ConnectionState ConnectionState
57111
get { return ConnectionState.PreConnecting; }
58112
}
59113

114+
public virtual IPEndPoint EndPoint
115+
{
116+
get { throw new NotSupportedException("An echo connection doesn't have an endpoint"); }
117+
}
118+
119+
public virtual string ErrorMessage
120+
{
121+
get { return null; }
122+
}
123+
60124
public virtual void Send(int frame, List<byte[]> orders)
61125
{
62126
var ms = new MemoryStream();
@@ -138,35 +202,100 @@ public void Dispose()
138202

139203
sealed class NetworkConnection : EchoConnection
140204
{
141-
readonly TcpClient tcp;
205+
readonly ConnectionTarget target;
206+
TcpClient tcp;
207+
IPEndPoint endpoint;
142208
readonly List<byte[]> queuedSyncPackets = new List<byte[]>();
143209
volatile ConnectionState connectionState = ConnectionState.Connecting;
144210
volatile int clientId;
145211
bool disposed;
212+
string errorMessage;
213+
214+
public override IPEndPoint EndPoint { get { return endpoint; } }
146215

147-
public NetworkConnection(string host, int port)
216+
public override string ErrorMessage { get { return errorMessage; } }
217+
218+
public NetworkConnection(ConnectionTarget target)
148219
{
149-
try
220+
this.target = target;
221+
new Thread(NetworkConnectionConnect)
222+
{
223+
Name = "{0} (connect to {1})".F(GetType().Name, target),
224+
IsBackground = true
225+
}.Start();
226+
}
227+
228+
void NetworkConnectionConnect()
229+
{
230+
var queue = new BlockingCollection<TcpClient>();
231+
232+
var atLeastOneEndpoint = false;
233+
foreach (var endpoint in target.GetConnectEndPoints())
234+
{
235+
atLeastOneEndpoint = true;
236+
new Thread(() =>
237+
{
238+
try
239+
{
240+
var client = new TcpClient(endpoint.AddressFamily) { NoDelay = true };
241+
client.Connect(endpoint.Address, endpoint.Port);
242+
243+
try
244+
{
245+
queue.Add(client);
246+
}
247+
catch (InvalidOperationException)
248+
{
249+
// Another connection was faster, close this one.
250+
client.Close();
251+
}
252+
}
253+
catch (Exception ex)
254+
{
255+
errorMessage = "Failed to connect to {0}".F(endpoint);
256+
Log.Write("client", "Failed to connect to {0}: {1}".F(endpoint, ex.Message));
257+
}
258+
})
259+
{
260+
Name = "{0} (connect to {1})".F(GetType().Name, endpoint),
261+
IsBackground = true
262+
}.Start();
263+
}
264+
265+
if (!atLeastOneEndpoint)
266+
{
267+
errorMessage = "Failed to resolve addresses for {0}".F(target);
268+
connectionState = ConnectionState.NotConnected;
269+
}
270+
271+
// Wait up to 5s for a successful connection. This should hopefully be enough because such high latency makes the game unplayable anyway.
272+
else if (queue.TryTake(out tcp, 5000))
150273
{
151-
tcp = new TcpClient(host, port) { NoDelay = true };
274+
// Copy endpoint here to have it even after getting disconnected.
275+
endpoint = (IPEndPoint)tcp.Client.RemoteEndPoint;
276+
152277
new Thread(NetworkConnectionReceive)
153278
{
154-
Name = GetType().Name + " " + host + ":" + port,
279+
Name = "{0} (receive from {1})".F(GetType().Name, tcp.Client.RemoteEndPoint),
155280
IsBackground = true
156-
}.Start(tcp.GetStream());
281+
}.Start();
157282
}
158-
catch
283+
else
159284
{
160285
connectionState = ConnectionState.NotConnected;
161286
}
287+
288+
// Close all unneeded connections in the queue and make sure new ones are closed on the connect thread.
289+
queue.CompleteAdding();
290+
foreach (var client in queue)
291+
client.Close();
162292
}
163293

164-
void NetworkConnectionReceive(object networkStreamObject)
294+
void NetworkConnectionReceive()
165295
{
166296
try
167297
{
168-
var networkStream = (NetworkStream)networkStreamObject;
169-
var reader = new BinaryReader(networkStream);
298+
var reader = new BinaryReader(tcp.GetStream());
170299
var handshakeProtocol = reader.ReadInt32();
171300

172301
if (handshakeProtocol != ProtocolVersion.Handshake)
@@ -187,7 +316,11 @@ void NetworkConnectionReceive(object networkStreamObject)
187316
AddPacket(new ReceivedPacket { FromClient = client, Data = buf });
188317
}
189318
}
190-
catch { }
319+
catch (Exception ex)
320+
{
321+
errorMessage = "Connection to {0} failed".F(endpoint);
322+
Log.Write("client", "Connection to {0} failed: {1}".F(endpoint, ex.Message));
323+
}
191324
finally
192325
{
193326
connectionState = ConnectionState.NotConnected;

Diff for: OpenRA.Game/Network/OrderManager.cs

+4-6
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@ public sealed class OrderManager : IDisposable
2828
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
2929
public World World;
3030

31-
public readonly string Host;
32-
public readonly int Port;
31+
public readonly ConnectionTarget Endpoint;
3332
public readonly string Password = "";
3433

35-
public string ServerError = "Server is not responding";
34+
public string ServerError = null;
3635
public bool AuthenticationFailed = false;
3736
public ExternalMod ServerExternalMod = null;
3837

@@ -80,10 +79,9 @@ public void StartGame()
8079
Connection.Send(i, new List<byte[]>());
8180
}
8281

83-
public OrderManager(string host, int port, string password, IConnection conn)
82+
public OrderManager(ConnectionTarget endpoint, string password, IConnection conn)
8483
{
85-
Host = host;
86-
Port = port;
84+
Endpoint = endpoint;
8785
Password = password;
8886
Connection = conn;
8987
syncReport = new SyncReport(this);

Diff for: OpenRA.Game/Network/ReplayConnection.cs

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System;
1313
using System.Collections.Generic;
1414
using System.IO;
15+
using System.Net;
1516
using OpenRA.FileFormats;
1617
using OpenRA.Primitives;
1718

@@ -32,6 +33,13 @@ class Chunk
3233

3334
public int LocalClientId { get { return -1; } }
3435
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
36+
public IPEndPoint EndPoint
37+
{
38+
get { throw new NotSupportedException("A replay connection doesn't have an endpoint"); }
39+
}
40+
41+
public string ErrorMessage { get { return null; } }
42+
3543
public readonly int TickCount;
3644
public readonly int FinalGameTick;
3745
public readonly bool IsValid;

0 commit comments

Comments
 (0)