Skip to content

Commit a212f36

Browse files
feat: Add support for connecting via a hostname instead of IP [MTT-10139] (#3440)
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 of #3441 --------- Co-authored-by: NoelStephensUnity <[email protected]>
1 parent 099d0b5 commit a212f36

File tree

8 files changed

+226
-38
lines changed

8 files changed

+226
-38
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ 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. (#3440)
14+
1315
### Fixed
1416

1517
- Fixed issue where when a client changes ownership via RPC the `NetworkBehaviour.OnOwnershipChanged` can result in identical previous and current owner identifiers. (#3434)

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

Lines changed: 88 additions & 18 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 UnityEngine;
1114
using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent;
1215
using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent;
@@ -310,26 +313,41 @@ public struct ConnectionAddressData
310313
[SerializeField]
311314
public string ServerListenAddress;
312315

313-
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port, bool silent = false)
316+
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
314317
{
315318
NetworkEndpoint endpoint = default;
316-
317-
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4) &&
318-
!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6))
319+
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4))
319320
{
320-
if (!silent)
321-
{
322-
Debug.LogError($"Invalid network endpoint: {ip}:{port}.");
323-
}
321+
NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6);
324322
}
325-
326323
return endpoint;
327324
}
328325

326+
private void InvalidEndpointError()
327+
{
328+
Debug.LogError($"Invalid network endpoint: {Address}:{Port}.");
329+
}
330+
329331
/// <summary>
330332
/// Endpoint (IP address and port) clients will connect to.
331333
/// </summary>
332-
public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port);
334+
public NetworkEndpoint ServerEndPoint
335+
{
336+
get
337+
{
338+
var networkEndpoint = ParseNetworkEndpoint(Address, Port);
339+
if (networkEndpoint == default)
340+
{
341+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
342+
if (!IsValidFqdn(Address))
343+
#endif
344+
{
345+
InvalidEndpointError();
346+
}
347+
}
348+
return networkEndpoint;
349+
}
350+
}
333351

334352
/// <summary>
335353
/// Endpoint (IP address and port) server will listen/bind on.
@@ -338,27 +356,35 @@ public NetworkEndpoint ListenEndPoint
338356
{
339357
get
340358
{
359+
NetworkEndpoint endpoint = default;
341360
if (string.IsNullOrEmpty(ServerListenAddress))
342361
{
343-
var ep = NetworkEndpoint.LoopbackIpv4;
362+
endpoint = NetworkEndpoint.LoopbackIpv4;
344363

345364
// If an address was entered and it's IPv6, switch to using ::1 as the
346365
// default listen address. (Otherwise we always assume IPv4.)
347366
if (!string.IsNullOrEmpty(Address) && ServerEndPoint.Family == NetworkFamily.Ipv6)
348367
{
349-
ep = NetworkEndpoint.LoopbackIpv6;
368+
endpoint = NetworkEndpoint.LoopbackIpv6;
350369
}
351-
352-
return ep.WithPort(Port);
370+
endpoint = endpoint.WithPort(Port);
353371
}
354372
else
355373
{
356-
return ParseNetworkEndpoint(ServerListenAddress, Port);
374+
endpoint = ParseNetworkEndpoint(ServerListenAddress, Port);
375+
if (endpoint == default)
376+
{
377+
InvalidEndpointError();
378+
}
357379
}
380+
return endpoint;
358381
}
359382
}
360383

361-
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && ParseNetworkEndpoint(Address, Port, true).Family == NetworkFamily.Ipv6;
384+
/// <summary>
385+
/// Returns true if the end point address is of type <see cref="NetworkFamily.Ipv6"/>.
386+
/// </summary>
387+
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv6);
362388
}
363389

364390

@@ -517,6 +543,16 @@ private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery)
517543
}
518544
}
519545

546+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
547+
private static bool IsValidFqdn(string fqdn)
548+
{
549+
// Regular expression to validate FQDN
550+
string pattern = @"^(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.(?!-)(?:[A-Za-z0-9-]{1,63}\.?)+[A-Za-z]{2,6}$";
551+
var regex = new Regex(pattern);
552+
return regex.IsMatch(fqdn);
553+
}
554+
#endif
555+
520556
private bool ClientBindAndConnect()
521557
{
522558
var serverEndpoint = default(NetworkEndpoint);
@@ -539,11 +575,31 @@ private bool ClientBindAndConnect()
539575
serverEndpoint = ConnectionData.ServerEndPoint;
540576
}
541577

578+
NetworkConnection serverConnection;
579+
542580
// Verify the endpoint is valid before proceeding
543581
if (serverEndpoint.Family == NetworkFamily.Invalid)
544582
{
583+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
584+
// If it's not valid, assure it meets FQDN standards
585+
if (IsValidFqdn(ConnectionData.Address))
586+
{
587+
// If so, then proceed with driver initialization and attempt to connect
588+
InitDriver();
589+
serverConnection = m_Driver.Connect(ConnectionData.Address, ConnectionData.Port);
590+
m_ServerClientId = ParseClientId(serverConnection);
591+
return true;
592+
}
593+
else
594+
{
595+
// If not then log an error and return false
596+
Debug.LogError($"Target server network address ({ConnectionData.Address}) is not a valid Fully Qualified Domain Name!");
597+
return false;
598+
}
599+
#else
545600
Debug.LogError($"Target server network address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
546601
return false;
602+
#endif
547603
}
548604

549605
InitDriver();
@@ -556,7 +612,7 @@ private bool ClientBindAndConnect()
556612
return false;
557613
}
558614

559-
var serverConnection = m_Driver.Connect(serverEndpoint);
615+
serverConnection = m_Driver.Connect(serverEndpoint);
560616
m_ServerClientId = ParseClientId(serverConnection);
561617

562618
return true;
@@ -567,8 +623,22 @@ private bool ServerBindAndListen(NetworkEndpoint endPoint)
567623
// Verify the endpoint is valid before proceeding
568624
if (endPoint.Family == NetworkFamily.Invalid)
569625
{
626+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
627+
// If it's not valid, assure it meets FQDN standards
628+
if (!IsValidFqdn(ConnectionData.Address))
629+
{
630+
// If not then log an error and return false
631+
Debug.LogError($"Listen network address ({ConnectionData.Address}) is not a valid {NetworkFamily.Ipv4} or {NetworkFamily.Ipv6} address!");
632+
}
633+
else
634+
{
635+
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!");
636+
}
637+
return false;
638+
#else
570639
Debug.LogError($"Network listen address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
571640
return false;
641+
#endif
572642
}
573643

574644
InitDriver();
@@ -647,7 +717,7 @@ public void SetClientRelayData(string ipAddress, ushort port, byte[] allocationI
647717
/// <summary>
648718
/// Sets IP and Port information. This will be ignored if using the Unity Relay and you should call <see cref="SetRelayServerData"/>
649719
/// </summary>
650-
/// <param name="ipv4Address">The remote IP address (despite the name, can be an IPv6 address)</param>
720+
/// <param name="ipv4Address">The remote IP address (despite the name, can be an IPv6 address or a domain name)</param>
651721
/// <param name="port">The remote port</param>
652722
/// <param name="listenAddress">The local listen address</param>
653723
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
@@ -42,10 +42,20 @@
4242
"expression": "2.1.0",
4343
"define": "UTP_TRANSPORT_2_1_ABOVE"
4444
},
45+
{
46+
"name": "com.unity.transport",
47+
"expression": "2.4.0",
48+
"define": "UTP_TRANSPORT_2_4_ABOVE"
49+
},
4550
{
4651
"name": "Unity",
4752
"expression": "2023",
4853
"define": "UNITY_DEDICATED_SERVER_ARGUMENTS_PRESENT"
54+
},
55+
{
56+
"name": "Unity",
57+
"expression": "6000.1.0a1",
58+
"define": "HOSTNAME_RESOLUTION_AVAILABLE"
4959
}
5060
]
5161
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,12 @@ public void UnityTransport_RestartSucceedsAfterFailure()
120120
Assert.False(transport.StartServer());
121121

122122
LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242.");
123+
124+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
125+
LogAssert.Expect(LogType.Error, $"Listen network address (127.0.0.) is not a valid {Networking.Transport.NetworkFamily.Ipv4} or {Networking.Transport.NetworkFamily.Ipv6} address!");
126+
#else
123127
LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!");
128+
#endif
124129

125130
transport.SetConnectionData("127.0.0.1", 4242, "127.0.0.1");
126131
Assert.True(transport.StartServer());
@@ -150,9 +155,12 @@ public void UnityTransport_StartClientFailsWithBadAddress()
150155

151156
transport.SetConnectionData("foobar", 4242);
152157
Assert.False(transport.StartClient());
153-
154158
LogAssert.Expect(LogType.Error, "Invalid network endpoint: foobar:4242.");
159+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
160+
LogAssert.Expect(LogType.Error, "Target server network address (foobar) is not a valid Fully Qualified Domain Name!");
161+
#else
155162
LogAssert.Expect(LogType.Error, "Target server network address (foobar) is Invalid!");
163+
#endif
156164

157165
transport.Shutdown();
158166
}

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
@@ -38,6 +38,16 @@
3838
"name": "com.unity.transport",
3939
"expression": "2.0.0-exp",
4040
"define": "UTP_TRANSPORT_2_0_ABOVE"
41+
},
42+
{
43+
"name": "com.unity.transport",
44+
"expression": "2.4.0",
45+
"define": "UTP_TRANSPORT_2_4_ABOVE"
46+
},
47+
{
48+
"name": "Unity",
49+
"expression": "6000.1.0a1",
50+
"define": "HOSTNAME_RESOLUTION_AVAILABLE"
4151
}
4252
]
4353
}

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class UnityTransportConnectionTests
2222
[UnityTearDown]
2323
public IEnumerator Cleanup()
2424
{
25+
VerboseDebug = false;
2526
if (m_Server)
2627
{
2728
m_Server.Shutdown();
@@ -57,8 +58,19 @@ public void DetectInvalidEndpoint()
5758
m_Clients[0].ConnectionData.Address = "MoreFubar";
5859
Assert.False(m_Server.StartServer(), "Server failed to detect invalid endpoint!");
5960
Assert.False(m_Clients[0].StartClient(), "Client failed to detect invalid endpoint!");
61+
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
62+
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!");
63+
LogAssert.Expect(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is not a valid Fully Qualified Domain Name!");
64+
65+
m_Server.ConnectionData.Address = "my.fubar.com";
66+
m_Server.ConnectionData.ServerListenAddress = "my.fubar.com";
67+
Assert.False(m_Server.StartServer(), "Server failed to detect invalid endpoint!");
68+
LogAssert.Expect(LogType.Error, $"While ({m_Server.ConnectionData.Address}) is a valid Fully Qualified Domain Name, you must use a " +
69+
$"valid {Networking.Transport.NetworkFamily.Ipv4} or {Networking.Transport.NetworkFamily.Ipv6} address when binding and listening for connections!");
70+
#else
6071
netcodeLogAssert.LogWasReceived(LogType.Error, $"Network listen address ({m_Server.ConnectionData.Address}) is Invalid!");
6172
netcodeLogAssert.LogWasReceived(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is Invalid!");
73+
#endif
6274
}
6375

6476
// Check connection with a single client.
@@ -186,35 +198,36 @@ public IEnumerator ClientDisconnectSingleClient()
186198
[UnityTest]
187199
public IEnumerator ClientDisconnectMultipleClients()
188200
{
189-
InitializeTransport(out m_Server, out m_ServerEvents);
190-
m_Server.StartServer();
201+
VerboseDebug = true;
202+
InitializeTransport(out m_Server, out m_ServerEvents, identifier: "Server");
203+
Assert.True(m_Server.StartServer(), "Failed to start server!");
191204

192205
for (int i = 0; i < k_NumClients; i++)
193206
{
194-
InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]);
195-
m_Clients[i].StartClient();
207+
InitializeTransport(out m_Clients[i], out m_ClientsEvents[i], identifier: $"Client-{i + 1}");
208+
Assert.True(m_Clients[i].StartClient(), $"Failed to start client-{i + 1}");
209+
// Assure all clients have connected before disconnecting them
210+
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[i], 5);
196211
}
197212

198-
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[k_NumClients - 1]);
199-
200213
// Disconnect a single client.
214+
VerboseLog($"Disconnecting Client-1");
201215
m_Clients[0].DisconnectLocalClient();
202216

203-
yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents);
217+
yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5);
204218

205219
// Disconnect all the other clients.
206220
for (int i = 1; i < k_NumClients; i++)
207221
{
222+
VerboseLog($"Disconnecting Client-{i + 1}");
208223
m_Clients[i].DisconnectLocalClient();
209224
}
210225

211-
yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents);
226+
yield return WaitForMultipleNetworkEvents(NetworkEvent.Disconnect, m_ServerEvents, 4, 20);
212227

213228
// Check that we got the correct number of Disconnect events on the server.
214229
Assert.AreEqual(k_NumClients * 2, m_ServerEvents.Count);
215230
Assert.AreEqual(k_NumClients, m_ServerEvents.Count(e => e.Type == NetworkEvent.Disconnect));
216-
217-
yield return null;
218231
}
219232

220233
// Check that server re-disconnects are no-ops.

0 commit comments

Comments
 (0)