Skip to content

Commit 48a045a

Browse files
feat: Add support for connecting via a hostname instead of IP (#3441)
Add support for connecting via a hostname instead of IP when using supported versions of unity and unity transport. ## Changelog - Added: When using UnityTransport >=2.4 and Unity >= 6000.1.0a1, `SetConnectionData` will accept a fully qualified hostname instead of an IP as a connect address on the client side. ## Testing and Documentation - No tests have been added. (Manual tests were performed. The CI environment doesn't have "localhost" consistently defined to be able to run automated tests.) - Includes edits to existing public API documentation. ## Backport Backport: #3440 --------- Co-authored-by: NoelStephensUnity <[email protected]>
1 parent 9370656 commit 48a045a

File tree

8 files changed

+223
-38
lines changed

8 files changed

+223
-38
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13+
- When using UnityTransport >=2.4 and Unity >= 6000.1.0a1, SetConnectionData will accept a fully qualified hostname instead of an IP as a connect address on the client side. (#3441)
1314

1415
### Fixed
1516

com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
using System;
99
using System.Collections.Generic;
10+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
11+
using System.Text.RegularExpressions;
12+
#endif
1013
using Unity.Burst;
1114
using Unity.Collections;
1215
using Unity.Collections.LowLevel.Unsafe;
@@ -249,26 +252,41 @@ public struct ConnectionAddressData
249252
[SerializeField]
250253
public string ServerListenAddress;
251254

252-
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port, bool silent = false)
255+
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
253256
{
254257
NetworkEndpoint endpoint = default;
255-
256-
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4) &&
257-
!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6))
258+
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4))
258259
{
259-
if (!silent)
260-
{
261-
Debug.LogError($"Invalid network endpoint: {ip}:{port}.");
262-
}
260+
NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6);
263261
}
264-
265262
return endpoint;
266263
}
267264

265+
private void InvalidEndpointError()
266+
{
267+
Debug.LogError($"Invalid network endpoint: {Address}:{Port}.");
268+
}
269+
268270
/// <summary>
269271
/// Endpoint (IP address and port) clients will connect to.
270272
/// </summary>
271-
public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port);
273+
public NetworkEndpoint ServerEndPoint
274+
{
275+
get
276+
{
277+
var networkEndpoint = ParseNetworkEndpoint(Address, Port);
278+
if (networkEndpoint == default)
279+
{
280+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
281+
if (!IsValidFqdn(Address))
282+
#endif
283+
{
284+
InvalidEndpointError();
285+
}
286+
}
287+
return networkEndpoint;
288+
}
289+
}
272290

273291
/// <summary>
274292
/// Endpoint (IP address and port) server will listen/bind on.
@@ -277,30 +295,35 @@ public NetworkEndpoint ListenEndPoint
277295
{
278296
get
279297
{
298+
NetworkEndpoint endpoint = default;
280299
if (string.IsNullOrEmpty(ServerListenAddress))
281300
{
282-
var ep = NetworkEndpoint.LoopbackIpv4;
301+
endpoint = NetworkEndpoint.LoopbackIpv4;
283302

284303
// If an address was entered and it's IPv6, switch to using ::1 as the
285304
// default listen address. (Otherwise we always assume IPv4.)
286305
if (!string.IsNullOrEmpty(Address) && ServerEndPoint.Family == NetworkFamily.Ipv6)
287306
{
288-
ep = NetworkEndpoint.LoopbackIpv6;
307+
endpoint = NetworkEndpoint.LoopbackIpv6;
289308
}
290-
291-
return ep.WithPort(Port);
309+
endpoint = endpoint.WithPort(Port);
292310
}
293311
else
294312
{
295-
return ParseNetworkEndpoint(ServerListenAddress, Port);
313+
endpoint = ParseNetworkEndpoint(ServerListenAddress, Port);
314+
if (endpoint == default)
315+
{
316+
InvalidEndpointError();
317+
}
296318
}
319+
return endpoint;
297320
}
298321
}
299322

300323
/// <summary>
301324
/// Returns true if the end point address is of type <see cref="NetworkFamily.Ipv6"/>.
302325
/// </summary>
303-
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && ParseNetworkEndpoint(Address, Port, true).Family == NetworkFamily.Ipv6;
326+
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv6);
304327
}
305328

306329

@@ -486,6 +509,15 @@ private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery)
486509
return NetworkPipeline.Null;
487510
}
488511
}
512+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
513+
private static bool IsValidFqdn(string fqdn)
514+
{
515+
// Regular expression to validate FQDN
516+
string pattern = @"^(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.(?!-)(?:[A-Za-z0-9-]{1,63}\.?)+[A-Za-z]{2,6}$";
517+
var regex = new Regex(pattern);
518+
return regex.IsMatch(fqdn);
519+
}
520+
#endif
489521

490522
private bool ClientBindAndConnect()
491523
{
@@ -512,8 +544,26 @@ private bool ClientBindAndConnect()
512544
// Verify the endpoint is valid before proceeding
513545
if (serverEndpoint.Family == NetworkFamily.Invalid)
514546
{
547+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
548+
549+
// If it's not valid, assure it meets FQDN standards
550+
if (IsValidFqdn(ConnectionData.Address))
551+
{
552+
// If so, then proceed with driver initialization and attempt to connect
553+
InitDriver();
554+
m_Driver.Connect(ConnectionData.Address, ConnectionData.Port);
555+
return true;
556+
}
557+
else
558+
{
559+
// If not then log an error and return false
560+
Debug.LogError($"Target server network address ({ConnectionData.Address}) is not a valid Fully Qualified Domain Name!");
561+
return false;
562+
}
563+
#else
515564
Debug.LogError($"Target server network address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
516565
return false;
566+
#endif
517567
}
518568

519569
InitDriver();
@@ -546,8 +596,22 @@ private bool ServerBindAndListen(NetworkEndpoint endPoint)
546596
// Verify the endpoint is valid before proceeding
547597
if (endPoint.Family == NetworkFamily.Invalid)
548598
{
599+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
600+
// If it's not valid, assure it meets FQDN standards
601+
if (!IsValidFqdn(ConnectionData.Address))
602+
{
603+
// If not then log an error and return false
604+
Debug.LogError($"Listen network address ({ConnectionData.Address}) is not a valid {NetworkFamily.Ipv4} or {NetworkFamily.Ipv6} address!");
605+
}
606+
else
607+
{
608+
Debug.LogError($"While ({ConnectionData.Address}) is a valid Fully Qualified Domain Name, you must use a valid {NetworkFamily.Ipv4} or {NetworkFamily.Ipv6} address when binding and listening for connections!");
609+
}
610+
return false;
611+
#else
549612
Debug.LogError($"Network listen address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
550613
return false;
614+
#endif
551615
}
552616

553617
InitDriver();
@@ -625,7 +689,7 @@ public void SetClientRelayData(string ipAddress, ushort port, byte[] allocationI
625689
/// <summary>
626690
/// Sets IP and Port information. This will be ignored if using the Unity Relay and you should call <see cref="SetRelayServerData"/>
627691
/// </summary>
628-
/// <param name="ipv4Address">The remote IP address (despite the name, can be an IPv6 address)</param>
692+
/// <param name="ipv4Address">The remote IP address (despite the name, can be an IPv6 address or a domain name)</param>
629693
/// <param name="port">The remote port</param>
630694
/// <param name="listenAddress">The local listen address</param>
631695
public void SetConnectionData(string ipv4Address, ushort port, string listenAddress = null)

com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@
6767
"name": "Unity",
6868
"expression": "6000.0.11f1",
6969
"define": "COM_UNITY_MODULES_PHYSICS2D_LINEAR"
70+
},
71+
{
72+
"name": "com.unity.transport",
73+
"expression": "2.4.0",
74+
"define": "UTP_TRANSPORT_2_4_ABOVE"
75+
},
76+
{
77+
"name": "Unity",
78+
"expression": "6000.1.0a1",
79+
"define": "HOSTNAME_RESOLUTION_AVAILABLE"
7080
}
7181
],
7282
"noEngineReferences": false

com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,13 @@ public void UnityTransport_RestartSucceedsAfterFailure()
130130
transport.SetConnectionData("127.0.0.", 4242, "127.0.0.");
131131

132132
Assert.False(transport.StartServer());
133-
134133
LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242.");
135-
LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!");
136134

135+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
136+
LogAssert.Expect(LogType.Error, "Listen network address (127.0.0.) is not a valid Ipv4 or Ipv6 address!");
137+
#else
138+
LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!");
139+
#endif
137140
transport.SetConnectionData("127.0.0.1", 4242, "127.0.0.1");
138141
Assert.True(transport.StartServer());
139142

@@ -162,10 +165,12 @@ public void UnityTransport_StartClientFailsWithBadAddress()
162165

163166
transport.SetConnectionData("foobar", 4242);
164167
Assert.False(transport.StartClient());
165-
166168
LogAssert.Expect(LogType.Error, "Invalid network endpoint: foobar:4242.");
169+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
170+
LogAssert.Expect(LogType.Error, "Target server network address (foobar) is not a valid Fully Qualified Domain Name!");
171+
#else
167172
LogAssert.Expect(LogType.Error, "Target server network address (foobar) is Invalid!");
168-
173+
#endif
169174
transport.Shutdown();
170175
}
171176

com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@
3333
"name": "Unity",
3434
"expression": "(0,2022.2.0a5)",
3535
"define": "UNITY_UNET_PRESENT"
36+
},
37+
{
38+
"name": "com.unity.transport",
39+
"expression": "2.4.0",
40+
"define": "UTP_TRANSPORT_2_4_ABOVE"
41+
},
42+
{
43+
"name": "Unity",
44+
"expression": "6000.1.0a1",
45+
"define": "HOSTNAME_RESOLUTION_AVAILABLE"
3646
}
3747
]
3848
}

com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public void OneTimeSetup()
3030
[UnityTearDown]
3131
public IEnumerator Cleanup()
3232
{
33+
VerboseDebug = false;
3334
if (m_Server)
3435
{
3536
m_Server.Shutdown();
@@ -66,8 +67,20 @@ public void DetectInvalidEndpoint()
6667
m_Clients[0].ConnectionData.Address = "MoreFubar";
6768
Assert.False(m_Server.StartServer(), "Server failed to detect invalid endpoint!");
6869
Assert.False(m_Clients[0].StartClient(), "Client failed to detect invalid endpoint!");
70+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
71+
LogAssert.Expect(LogType.Error, $"Listen network address ({m_Server.ConnectionData.Address}) is not a valid {Networking.Transport.NetworkFamily.Ipv4} or {Networking.Transport.NetworkFamily.Ipv6} address!");
72+
LogAssert.Expect(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is not a valid Fully Qualified Domain Name!");
73+
74+
m_Server.ConnectionData.Address = "my.fubar.com";
75+
m_Server.ConnectionData.ServerListenAddress = "my.fubar.com";
76+
Assert.False(m_Server.StartServer(), "Server failed to detect invalid endpoint!");
77+
LogAssert.Expect(LogType.Error, $"While ({m_Server.ConnectionData.Address}) is a valid Fully Qualified Domain Name, you must use a " +
78+
$"valid {Networking.Transport.NetworkFamily.Ipv4} or {Networking.Transport.NetworkFamily.Ipv6} address when binding and listening for connections!");
79+
#else
6980
netcodeLogAssert.LogWasReceived(LogType.Error, $"Network listen address ({m_Server.ConnectionData.Address}) is Invalid!");
7081
netcodeLogAssert.LogWasReceived(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is Invalid!");
82+
#endif
83+
7184
UnityTransportTestComponent.CleanUp();
7285
}
7386

@@ -194,26 +207,32 @@ public IEnumerator ClientDisconnectSingleClient()
194207
[UnityTest]
195208
public IEnumerator ClientDisconnectMultipleClients()
196209
{
197-
InitializeTransport(out m_Server, out m_ServerEvents);
198-
m_Server.StartServer();
210+
VerboseDebug = true;
211+
InitializeTransport(out m_Server, out m_ServerEvents, identifier: "Server");
212+
Assert.True(m_Server.StartServer(), "Failed to start server!");
213+
199214
for (int i = 0; i < k_NumClients; i++)
200215
{
201-
InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]);
202-
m_Clients[i].StartClient();
216+
InitializeTransport(out m_Clients[i], out m_ClientsEvents[i], identifier: $"Client-{i + 1}");
217+
Assert.True(m_Clients[i].StartClient(), $"Failed to start client-{i + 1}");
218+
// Assure all clients have connected before disconnecting them
219+
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[i], 5);
203220
}
204-
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[k_NumClients - 1]);
205221

206222
// Disconnect a single client.
223+
VerboseLog($"Disconnecting Client-1");
207224
m_Clients[0].DisconnectLocalClient();
208225

209-
yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents);
226+
yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5);
210227

211228
// Disconnect all the other clients.
212229
for (int i = 1; i < k_NumClients; i++)
213230
{
231+
VerboseLog($"Disconnecting Client-{i + 1}");
214232
m_Clients[i].DisconnectLocalClient();
215233
}
216-
yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5);
234+
235+
yield return WaitForMultipleNetworkEvents(NetworkEvent.Disconnect, m_ServerEvents, 4, 20);
217236

218237
// Check that we got the correct number of Disconnect events on the server.
219238
Assert.AreEqual(k_NumClients * 2, m_ServerEvents.Count);

0 commit comments

Comments
 (0)