diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0e1bbc0de0..f5678d6cbf 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,14 +14,15 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed an issue where `UnityTransport` would not accept single words as valid hostnames (notably "localhost"). (#3591) - Fixed issue where viewing a `NetworkBehaviour` with one or more `NetworkVariable` fields could throw an exception if running a distributed authority network topology with a local (DAHost) host and viewed on the host when the host is not the authority of the associated `NetworkObject`. (#3578) - Fixed issue when using a distributed authority network topology and viewing a `NetworkBehaviour` with one or more `NetworkVariable` fields in the inspector view would not show editable fields. (#3578) ### Changed +- Marked `UnityTransport.ConnectionAddressData.ServerEndPoint` as obsolete. It can't work when using hostnames as the server address, and its functionality can easily be replicated using `NetworkEndpoint.Parse`. (#3591) - Optimized `NetworkList` indexer setter to skip operations when the new value equals the existing value, improving performance by avoiding unnecessary list events and network synchronization. (#3587) - ## [2.5.0] - 2025-08-01 ### Added diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 84513fde66..5bd985df5c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -7,9 +7,6 @@ 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; @@ -235,7 +232,7 @@ public struct ConnectionAddressData [SerializeField] public string ServerListenAddress; - private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port) + internal static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port) { NetworkEndpoint endpoint = default; if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4)) @@ -245,31 +242,16 @@ private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port) return endpoint; } - private void InvalidEndpointError() - { - Debug.LogError($"Invalid network endpoint: {Address}:{Port}."); - } - /// /// Endpoint (IP address and port) clients will connect to. /// - 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; - } - } + /// + /// If a DNS hostname was set as the address, this will return an invalid endpoint. This + /// is still handled correctly by NGO, but for this reason usage of this property is + /// discouraged. + /// + [Obsolete("Use NetworkEndpoint.Parse on the Address field instead.")] + public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port); /// /// Endpoint (IP address and port) server will listen/bind on. @@ -281,14 +263,7 @@ public NetworkEndpoint ListenEndPoint NetworkEndpoint endpoint = default; if (string.IsNullOrEmpty(ServerListenAddress)) { - 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) - { - endpoint = NetworkEndpoint.LoopbackIpv6; - } + endpoint = IsIpv6 ? NetworkEndpoint.LoopbackIpv6 : NetworkEndpoint.LoopbackIpv4; endpoint = endpoint.WithPort(Port); } else @@ -296,7 +271,7 @@ public NetworkEndpoint ListenEndPoint endpoint = ParseNetworkEndpoint(ServerListenAddress, Port); if (endpoint == default) { - InvalidEndpointError(); + Debug.LogError($"Invalid listen endpoint: {ServerListenAddress}:{Port}. Note that the listen endpoint MUST be an IP address (not a hostname)."); } } return endpoint; @@ -304,9 +279,11 @@ public NetworkEndpoint ListenEndPoint } /// - /// Returns true if the end point address is of type . + /// Returns true if the end point address is of type or + /// if it is a hostname (because in current versions of the engine, hostname resolution + /// prioritizes IPv6 addresses). /// - public bool IsIpv6 => !string.IsNullOrEmpty(Address) && NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv6); + public bool IsIpv6 => !string.IsNullOrEmpty(Address) && !NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv4); } @@ -667,16 +644,6 @@ private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery) } } -#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}(?A representing the connection to the server, or an invalid connection if the connection attempt fails. protected virtual NetworkConnection Connect(NetworkEndpoint serverEndpoint) { +#if HOSTNAME_RESOLUTION_AVAILABLE + // If the server endpoint is invalid, it means whatever the user entered in the address + // field was not an IP address, and must be presumed to be a hostname. + if (serverEndpoint.Family == NetworkFamily.Invalid) + { + return m_Driver.Connect(ConnectionData.Address, ConnectionData.Port); + } +#endif return m_Driver.Connect(serverEndpoint); } private bool ServerBindAndListen(NetworkEndpoint endPoint) { - // Verify the endpoint is valid before proceeding if (endPoint.Family == NetworkFamily.Invalid) { -#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE - // If it's not valid, assure it meets FQDN standards - if (!IsValidFqdn(ConnectionData.Address)) - { - // If not then log an error and return false - Debug.LogError($"Listen network address ({ConnectionData.Address}) is not a valid {NetworkFamily.Ipv4} or {NetworkFamily.Ipv6} address!"); - } - else - { - 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!"); - } return false; -#else - Debug.LogError($"Network listen address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!"); - return false; -#endif } InitDriver(); diff --git a/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef index e4de012706..d0a5743b42 100644 --- a/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef +++ b/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef @@ -68,11 +68,6 @@ "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", diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index 6762fc954d..bb43739976 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -127,16 +127,11 @@ public void UnityTransport_RestartSucceedsAfterFailure() UnityTransport transport = new GameObject().AddComponent(); transport.Initialize(); - transport.SetConnectionData("127.0.0.", 4242, "127.0.0."); + transport.SetConnectionData("127.0.0.1", 4242, "foobar"); Assert.False(transport.StartServer()); - LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242."); + LogAssert.Expect(LogType.Error, "Invalid listen endpoint: foobar:4242. Note that the listen endpoint MUST be an IP address (not a hostname)."); -#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()); @@ -156,24 +151,6 @@ public void UnityTransport_StartServerWithoutAddresses() transport.Shutdown(); } - // Check that StartClient returns false with bad connection data. - [Test] - public void UnityTransport_StartClientFailsWithBadAddress() - { - UnityTransport transport = new GameObject().AddComponent(); - transport.Initialize(); - - 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(); - } - [Test] public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] string cert, [Values("", null)] string secret) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Unity.Netcode.Editor.Tests.asmdef b/com.unity.netcode.gameobjects/Tests/Editor/Unity.Netcode.Editor.Tests.asmdef index 04ed44cf77..976fa7be17 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Unity.Netcode.Editor.Tests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Editor/Unity.Netcode.Editor.Tests.asmdef @@ -38,11 +38,6 @@ "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", diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs index 2771ae6299..d94e7ef0b9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.Transports.UTP; +using Unity.Networking.Transport; using UnityEngine; using UnityEngine.TestTools; using static Unity.Netcode.RuntimeTests.UnityTransportTestHelpers; @@ -55,45 +56,52 @@ public IEnumerator Cleanup() yield return null; } - // Check that invalid endpoint addresses are detected and return false if detected - [Test] - public void DetectInvalidEndpoint() + // Check connection with a single client (IP address). + [UnityTest] + public IEnumerator ConnectSingleClient_IPAddress() { - using var netcodeLogAssert = new NetcodeLogAssert(true); InitializeTransport(out m_Server, out m_ServerEvents); InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]); - m_Server.ConnectionData.Address = "Fubar"; - m_Server.ConnectionData.ServerListenAddress = "Fubar"; - 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(); + m_Clients[0].SetConnectionData("127.0.0.1", 7777); + + m_Server.StartServer(); + m_Clients[0].StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[0]); + + // Check we've received Connect event on server too. + Assert.AreEqual(1, m_ServerEvents.Count); + Assert.AreEqual(NetworkEvent.Connect, m_ServerEvents[0].Type); + + yield return null; } - // Check connection with a single client. +#if HOSTNAME_RESOLUTION_AVAILABLE + // Check connection with a single client (hostname). [UnityTest] - public IEnumerator ConnectSingleClient() + public IEnumerator ConnectSingleClient_Hostname() { InitializeTransport(out m_Server, out m_ServerEvents); InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]); - m_Server.StartServer(); + // We don't know if localhost will resolve to 127.0.0.1 or ::1, so we wait until we know + // before starting the server. Because localhost is pretty much always defined locally + // it should resolve immediatly and thus waiting one frame should be enough. + + // We'll need to retry connection requests most likely so make this fast. + m_Clients[0].ConnectTimeoutMS = 50; + + m_Clients[0].SetConnectionData("localhost", 7777); m_Clients[0].StartClient(); + yield return null; + + var endpoint = m_Clients[0].GetLocalEndpoint(); + var ip = endpoint.Family == NetworkFamily.Ipv4 ? "127.0.0.1" : "::1"; + m_Server.SetConnectionData(ip, 7777, ip); + m_Server.StartServer(); + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[0]); // Check we've received Connect event on server too. @@ -102,6 +110,7 @@ public IEnumerator ConnectSingleClient() yield return null; } +#endif // Check connection with multiple clients. [UnityTest] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef index 8a7507f158..d2b3924e2a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef @@ -48,11 +48,6 @@ "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",