diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index b817a0b6ea..3069602122 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### 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. (#3441)
### Fixed
diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs
index 210d7f660b..31b20b6630 100644
--- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs
@@ -7,6 +7,9 @@
using System;
using System.Collections.Generic;
+#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
+using System.Text.RegularExpressions;
+#endif
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
@@ -249,26 +252,41 @@ public struct ConnectionAddressData
[SerializeField]
public string ServerListenAddress;
- private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port, bool silent = false)
+ private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
{
NetworkEndpoint endpoint = default;
-
- if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4) &&
- !NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6))
+ if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4))
{
- if (!silent)
- {
- Debug.LogError($"Invalid network endpoint: {ip}:{port}.");
- }
+ NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6);
}
-
return endpoint;
}
+ private void InvalidEndpointError()
+ {
+ Debug.LogError($"Invalid network endpoint: {Address}:{Port}.");
+ }
+
///
/// Endpoint (IP address and port) clients will connect to.
///
- public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port);
+ public NetworkEndpoint ServerEndPoint
+ {
+ get
+ {
+ var networkEndpoint = ParseNetworkEndpoint(Address, Port);
+ if (networkEndpoint == default)
+ {
+#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
+ if (!IsValidFqdn(Address))
+#endif
+ {
+ InvalidEndpointError();
+ }
+ }
+ return networkEndpoint;
+ }
+ }
///
/// Endpoint (IP address and port) server will listen/bind on.
@@ -277,30 +295,35 @@ public NetworkEndpoint ListenEndPoint
{
get
{
+ NetworkEndpoint endpoint = default;
if (string.IsNullOrEmpty(ServerListenAddress))
{
- var ep = NetworkEndpoint.LoopbackIpv4;
+ endpoint = NetworkEndpoint.LoopbackIpv4;
// If an address was entered and it's IPv6, switch to using ::1 as the
// default listen address. (Otherwise we always assume IPv4.)
if (!string.IsNullOrEmpty(Address) && ServerEndPoint.Family == NetworkFamily.Ipv6)
{
- ep = NetworkEndpoint.LoopbackIpv6;
+ endpoint = NetworkEndpoint.LoopbackIpv6;
}
-
- return ep.WithPort(Port);
+ endpoint = endpoint.WithPort(Port);
}
else
{
- return ParseNetworkEndpoint(ServerListenAddress, Port);
+ endpoint = ParseNetworkEndpoint(ServerListenAddress, Port);
+ if (endpoint == default)
+ {
+ InvalidEndpointError();
+ }
}
+ return endpoint;
}
}
///
/// Returns true if the end point address is of type .
///
- public bool IsIpv6 => !string.IsNullOrEmpty(Address) && ParseNetworkEndpoint(Address, Port, true).Family == NetworkFamily.Ipv6;
+ public bool IsIpv6 => !string.IsNullOrEmpty(Address) && NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv6);
}
@@ -486,6 +509,15 @@ private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery)
return NetworkPipeline.Null;
}
}
+#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
+ private static bool IsValidFqdn(string fqdn)
+ {
+ // Regular expression to validate FQDN
+ string pattern = @"^(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?
/// Sets IP and Port information. This will be ignored if using the Unity Relay and you should call
///
- /// The remote IP address (despite the name, can be an IPv6 address)
+ /// The remote IP address (despite the name, can be an IPv6 address or a domain name)
/// The remote port
/// The local listen address
public void SetConnectionData(string ipv4Address, ushort port, string listenAddress = null)
diff --git a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef
index d68a562768..e4de012706 100644
--- a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef
+++ b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef
@@ -67,6 +67,16 @@
"name": "Unity",
"expression": "6000.0.11f1",
"define": "COM_UNITY_MODULES_PHYSICS2D_LINEAR"
+ },
+ {
+ "name": "com.unity.transport",
+ "expression": "2.4.0",
+ "define": "UTP_TRANSPORT_2_4_ABOVE"
+ },
+ {
+ "name": "Unity",
+ "expression": "6000.1.0a1",
+ "define": "HOSTNAME_RESOLUTION_AVAILABLE"
}
],
"noEngineReferences": false
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs
index bd9d9e937e..6762fc954d 100644
--- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs
@@ -130,10 +130,13 @@ public void UnityTransport_RestartSucceedsAfterFailure()
transport.SetConnectionData("127.0.0.", 4242, "127.0.0.");
Assert.False(transport.StartServer());
-
LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242.");
- LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!");
+#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
+ LogAssert.Expect(LogType.Error, "Listen network address (127.0.0.) is not a valid Ipv4 or Ipv6 address!");
+#else
+ LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!");
+#endif
transport.SetConnectionData("127.0.0.1", 4242, "127.0.0.1");
Assert.True(transport.StartServer());
@@ -162,10 +165,12 @@ public void UnityTransport_StartClientFailsWithBadAddress()
transport.SetConnectionData("foobar", 4242);
Assert.False(transport.StartClient());
-
LogAssert.Expect(LogType.Error, "Invalid network endpoint: foobar:4242.");
+#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
+ LogAssert.Expect(LogType.Error, "Target server network address (foobar) is not a valid Fully Qualified Domain Name!");
+#else
LogAssert.Expect(LogType.Error, "Target server network address (foobar) is Invalid!");
-
+#endif
transport.Shutdown();
}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef
index b2c942a948..56f600746b 100644
--- a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef
+++ b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef
@@ -33,6 +33,16 @@
"name": "Unity",
"expression": "(0,2022.2.0a5)",
"define": "UNITY_UNET_PRESENT"
+ },
+ {
+ "name": "com.unity.transport",
+ "expression": "2.4.0",
+ "define": "UTP_TRANSPORT_2_4_ABOVE"
+ },
+ {
+ "name": "Unity",
+ "expression": "6000.1.0a1",
+ "define": "HOSTNAME_RESOLUTION_AVAILABLE"
}
]
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs
index c0a13e2f0d..2771ae6299 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs
@@ -30,6 +30,7 @@ public void OneTimeSetup()
[UnityTearDown]
public IEnumerator Cleanup()
{
+ VerboseDebug = false;
if (m_Server)
{
m_Server.Shutdown();
@@ -66,8 +67,20 @@ public void DetectInvalidEndpoint()
m_Clients[0].ConnectionData.Address = "MoreFubar";
Assert.False(m_Server.StartServer(), "Server failed to detect invalid endpoint!");
Assert.False(m_Clients[0].StartClient(), "Client failed to detect invalid endpoint!");
+#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
+ 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!");
+ LogAssert.Expect(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is not a valid Fully Qualified Domain Name!");
+
+ m_Server.ConnectionData.Address = "my.fubar.com";
+ m_Server.ConnectionData.ServerListenAddress = "my.fubar.com";
+ Assert.False(m_Server.StartServer(), "Server failed to detect invalid endpoint!");
+ LogAssert.Expect(LogType.Error, $"While ({m_Server.ConnectionData.Address}) is a valid Fully Qualified Domain Name, you must use a " +
+ $"valid {Networking.Transport.NetworkFamily.Ipv4} or {Networking.Transport.NetworkFamily.Ipv6} address when binding and listening for connections!");
+#else
netcodeLogAssert.LogWasReceived(LogType.Error, $"Network listen address ({m_Server.ConnectionData.Address}) is Invalid!");
netcodeLogAssert.LogWasReceived(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is Invalid!");
+#endif
+
UnityTransportTestComponent.CleanUp();
}
@@ -194,26 +207,32 @@ public IEnumerator ClientDisconnectSingleClient()
[UnityTest]
public IEnumerator ClientDisconnectMultipleClients()
{
- InitializeTransport(out m_Server, out m_ServerEvents);
- m_Server.StartServer();
+ VerboseDebug = true;
+ InitializeTransport(out m_Server, out m_ServerEvents, identifier: "Server");
+ Assert.True(m_Server.StartServer(), "Failed to start server!");
+
for (int i = 0; i < k_NumClients; i++)
{
- InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]);
- m_Clients[i].StartClient();
+ InitializeTransport(out m_Clients[i], out m_ClientsEvents[i], identifier: $"Client-{i + 1}");
+ Assert.True(m_Clients[i].StartClient(), $"Failed to start client-{i + 1}");
+ // Assure all clients have connected before disconnecting them
+ yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[i], 5);
}
- yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[k_NumClients - 1]);
// Disconnect a single client.
+ VerboseLog($"Disconnecting Client-1");
m_Clients[0].DisconnectLocalClient();
- yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents);
+ yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5);
// Disconnect all the other clients.
for (int i = 1; i < k_NumClients; i++)
{
+ VerboseLog($"Disconnecting Client-{i + 1}");
m_Clients[i].DisconnectLocalClient();
}
- yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5);
+
+ yield return WaitForMultipleNetworkEvents(NetworkEvent.Disconnect, m_ServerEvents, 4, 20);
// Check that we got the correct number of Disconnect events on the server.
Assert.AreEqual(k_NumClients * 2, m_ServerEvents.Count);
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs
index 926b2fa7bc..91c33c0d7d 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs
@@ -19,21 +19,63 @@ internal static class UnityTransportTestHelpers
// Wait for an event to appear in the given event list (must be the very next event).
public static IEnumerator WaitForNetworkEvent(NetworkEvent type, List events, float timeout = MaxNetworkEventWaitTime)
{
- int initialCount = events.Count;
- float startTime = Time.realtimeSinceStartup;
-
- while (Time.realtimeSinceStartup - startTime < timeout)
+ var initialCount = events.Count;
+ var startTime = Time.realtimeSinceStartup + timeout;
+ var waitPeriod = new WaitForSeconds(0.01f);
+ var conditionMet = false;
+ while (startTime > Time.realtimeSinceStartup)
{
if (events.Count > initialCount)
{
Assert.AreEqual(type, events[initialCount].Type);
- yield break;
+ conditionMet = true;
+ break;
}
- yield return new WaitForSeconds(0.01f);
+ yield return waitPeriod;
+ }
+ if (!conditionMet)
+ {
+ Assert.Fail("Timed out while waiting for network event.");
}
+ }
- Assert.Fail("Timed out while waiting for network event.");
+ internal static IEnumerator WaitForMultipleNetworkEvents(NetworkEvent type, List events, int count, float timeout = MaxNetworkEventWaitTime)
+ {
+ var initialCount = events.Count;
+ var startTime = Time.realtimeSinceStartup + timeout;
+ var waitPeriod = new WaitForSeconds(0.01f);
+ var conditionMet = false;
+ while (startTime > Time.realtimeSinceStartup)
+ {
+ // Wait until we have received at least (count) number of events
+ if ((events.Count - initialCount) >= count)
+ {
+ var foundTypes = 0;
+ // Look through all events received to match against the type we
+ // are looking for.
+ for (int i = initialCount; i < initialCount + count; i++)
+ {
+ if (type.Equals(events[i].Type))
+ {
+ foundTypes++;
+ }
+ }
+ // If we reached the number of events we were expecting
+ conditionMet = foundTypes == count;
+ if (conditionMet)
+ {
+ // break from the wait loop
+ break;
+ }
+ }
+
+ yield return waitPeriod;
+ }
+ if (!conditionMet)
+ {
+ Assert.Fail("Timed out while waiting for network event.");
+ }
}
// Wait to ensure no event is sent.
@@ -53,12 +95,22 @@ public static IEnumerator EnsureNoNetworkEvent(List events, floa
}
}
-
// Common code to initialize a UnityTransport that logs its events.
- public static void InitializeTransport(out UnityTransport transport, out List events,
+ public static void InitializeTransport(out UnityTransport transport, out List events, int maxPayloadSize = UnityTransport.InitialMaxPayloadSize, int maxSendQueueSize = 0, NetworkFamily family = NetworkFamily.Ipv4)
+ {
+ InitializeTransport(out transport, out events, string.Empty, maxPayloadSize, maxSendQueueSize, family);
+ }
+
+ ///
+ /// Interanl version with identifier parameter
+ ///
+ internal static void InitializeTransport(out UnityTransport transport, out List events, string identifier,
int maxPayloadSize = UnityTransport.InitialMaxPayloadSize, int maxSendQueueSize = 0, NetworkFamily family = NetworkFamily.Ipv4)
{
- var logger = new TransportEventLogger();
+ var logger = new TransportEventLogger()
+ {
+ Identifier = identifier,
+ };
events = logger.Events;
transport = new GameObject().AddComponent();
@@ -75,6 +127,16 @@ public static void InitializeTransport(out UnityTransport transport, out List m_Events = new List();
public List Events => m_Events;
+
+ public string Identifier;
public void HandleEvent(NetworkEvent type, ulong clientID, ArraySegment data, float receiveTime)
{
+ VerboseLog($"[{Identifier}]Tansport Event][{type}][{receiveTime}] Client-{clientID}");
+
// Copy the data since the backing array will be reused for future messages.
if (data != default(ArraySegment))
{
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef
index ce8ab1c7eb..7153a15480 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef
@@ -48,6 +48,16 @@
"name": "com.unity.modules.physics",
"expression": "",
"define": "COM_UNITY_MODULES_PHYSICS"
+ },
+ {
+ "name": "com.unity.transport",
+ "expression": "2.4.0",
+ "define": "UTP_TRANSPORT_2_4_ABOVE"
+ },
+ {
+ "name": "Unity",
+ "expression": "6000.1.0a1",
+ "define": "HOSTNAME_RESOLUTION_AVAILABLE"
}
],
"noEngineReferences": false