Skip to content

Commit fde71e4

Browse files
Merge develop-2.0.0 into fix/avoid-unnecessry-gc-alloc
2 parents 3e32b8e + b21d4ad commit fde71e4

File tree

7 files changed

+108
-161
lines changed

7 files changed

+108
-161
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ Additional documentation and release notes are available at [Multiplayer Documen
1414

1515
### Fixed
1616

17+
- Fixed an issue where `UnityTransport` would not accept single words as valid hostnames (notably "localhost"). (#3591)
1718
- 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)
1819
- 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)
1920
- Fixed issue with unnecessary internal GC Allocations when using the `IReadOnlyList` `NetworkManager.ConnectedClientsIds` within a `foreach` statement by either replacing with a `for` loop or directly referencing the `NetworkConnectionManager.ConnectedClientIds`. (#3527)
2021

2122
### Changed
2223

24+
- 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)
2325
- Optimized `NetworkList<T>` indexer setter to skip operations when the new value equals the existing value, improving performance by avoiding unnecessary list events and network synchronization. (#3587)
2426

25-
2627
## [2.5.0] - 2025-08-01
2728

2829
### Added

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

Lines changed: 37 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
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
1310
using Unity.Burst;
1411
using Unity.Collections;
1512
using Unity.Collections.LowLevel.Unsafe;
@@ -235,7 +232,7 @@ public struct ConnectionAddressData
235232
[SerializeField]
236233
public string ServerListenAddress;
237234

238-
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
235+
internal static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
239236
{
240237
NetworkEndpoint endpoint = default;
241238
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4))
@@ -245,31 +242,16 @@ private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
245242
return endpoint;
246243
}
247244

248-
private void InvalidEndpointError()
249-
{
250-
Debug.LogError($"Invalid network endpoint: {Address}:{Port}.");
251-
}
252-
253245
/// <summary>
254246
/// Endpoint (IP address and port) clients will connect to.
255247
/// </summary>
256-
public NetworkEndpoint ServerEndPoint
257-
{
258-
get
259-
{
260-
var networkEndpoint = ParseNetworkEndpoint(Address, Port);
261-
if (networkEndpoint == default)
262-
{
263-
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
264-
if (!IsValidFqdn(Address))
265-
#endif
266-
{
267-
InvalidEndpointError();
268-
}
269-
}
270-
return networkEndpoint;
271-
}
272-
}
248+
/// <remarks>
249+
/// If a DNS hostname was set as the address, this will return an invalid endpoint. This
250+
/// is still handled correctly by NGO, but for this reason usage of this property is
251+
/// discouraged.
252+
/// </remarks>
253+
[Obsolete("Use NetworkEndpoint.Parse on the Address field instead.")]
254+
public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port);
273255

274256
/// <summary>
275257
/// Endpoint (IP address and port) server will listen/bind on.
@@ -281,32 +263,27 @@ public NetworkEndpoint ListenEndPoint
281263
NetworkEndpoint endpoint = default;
282264
if (string.IsNullOrEmpty(ServerListenAddress))
283265
{
284-
endpoint = NetworkEndpoint.LoopbackIpv4;
285-
286-
// If an address was entered and it's IPv6, switch to using ::1 as the
287-
// default listen address. (Otherwise we always assume IPv4.)
288-
if (!string.IsNullOrEmpty(Address) && ServerEndPoint.Family == NetworkFamily.Ipv6)
289-
{
290-
endpoint = NetworkEndpoint.LoopbackIpv6;
291-
}
266+
endpoint = IsIpv6 ? NetworkEndpoint.LoopbackIpv6 : NetworkEndpoint.LoopbackIpv4;
292267
endpoint = endpoint.WithPort(Port);
293268
}
294269
else
295270
{
296271
endpoint = ParseNetworkEndpoint(ServerListenAddress, Port);
297272
if (endpoint == default)
298273
{
299-
InvalidEndpointError();
274+
Debug.LogError($"Invalid listen endpoint: {ServerListenAddress}:{Port}. Note that the listen endpoint MUST be an IP address (not a hostname).");
300275
}
301276
}
302277
return endpoint;
303278
}
304279
}
305280

306281
/// <summary>
307-
/// Returns true if the end point address is of type <see cref="NetworkFamily.Ipv6"/>.
282+
/// Returns true if the end point address is of type <see cref="NetworkFamily.Ipv6"/> or
283+
/// if it is a hostname (because in current versions of the engine, hostname resolution
284+
/// prioritizes IPv6 addresses).
308285
/// </summary>
309-
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv6);
286+
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && !NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv4);
310287
}
311288

312289

@@ -667,16 +644,6 @@ private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery)
667644
}
668645
}
669646

670-
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
671-
private static bool IsValidFqdn(string fqdn)
672-
{
673-
// Regular expression to validate FQDN
674-
string pattern = @"^(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.(?!-)(?:[A-Za-z0-9-]{1,63}\.?)+[A-Za-z]{2,6}$";
675-
var regex = new Regex(pattern);
676-
return regex.IsMatch(fqdn);
677-
}
678-
#endif
679-
680647
private bool ClientBindAndConnect()
681648
{
682649
var serverEndpoint = default(NetworkEndpoint);
@@ -687,44 +654,28 @@ private bool ClientBindAndConnect()
687654
}
688655
else
689656
{
690-
serverEndpoint = ConnectionData.ServerEndPoint;
691-
}
692-
693-
// Verify the endpoint is valid before proceeding
694-
if (serverEndpoint.Family == NetworkFamily.Invalid)
695-
{
696-
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
657+
// This will result in an invalid endpoint if the address is a hostname.
658+
// This is handled later in the Connect method if hostname resolution is available
659+
// (although we still check for hostname validity to error out early if it's not),
660+
// but if not then we need to error out here.
661+
serverEndpoint = ConnectionAddressData.ParseNetworkEndpoint(ConnectionData.Address, ConnectionData.Port);
697662

698-
// If it's not valid, assure it meets FQDN standards
699-
if (IsValidFqdn(ConnectionData.Address))
663+
if (serverEndpoint.Family == NetworkFamily.Invalid)
700664
{
701-
// If so, then proceed with driver initialization and attempt to connect
702-
InitDriver();
703-
m_Driver.Connect(ConnectionData.Address, ConnectionData.Port);
704-
return true;
705-
}
706-
else
707-
{
708-
// If not then log an error and return false
709-
Debug.LogError($"Target server network address ({ConnectionData.Address}) is not a valid Fully Qualified Domain Name!");
710-
return false;
711-
}
665+
#if HOSTNAME_RESOLUTION_AVAILABLE
666+
if (Uri.CheckHostName(ConnectionData.Address) != UriHostNameType.Dns)
667+
{
668+
Debug.LogError($"Provided connection address \"{ConnectionData.Address}\" is not a valid hostname.");
669+
return false;
670+
}
712671
#else
713-
Debug.LogError($"Target server network address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
714-
return false;
672+
Debug.LogError($"Invalid server address: {ConnectionData.Address}:{ConnectionData.Port}.");
673+
return false;
715674
#endif
675+
}
716676
}
717677

718678
InitDriver();
719-
720-
var bindEndpoint = serverEndpoint.Family == NetworkFamily.Ipv6 ? NetworkEndpoint.AnyIpv6 : NetworkEndpoint.AnyIpv4;
721-
int result = m_Driver.Bind(bindEndpoint);
722-
if (result != 0)
723-
{
724-
Debug.LogError("Client failed to bind");
725-
return false;
726-
}
727-
728679
Connect(serverEndpoint);
729680

730681
return true;
@@ -737,30 +688,22 @@ private bool ClientBindAndConnect()
737688
/// <returns>A <see cref="NetworkConnection"/> representing the connection to the server, or an invalid connection if the connection attempt fails.</returns>
738689
protected virtual NetworkConnection Connect(NetworkEndpoint serverEndpoint)
739690
{
691+
#if HOSTNAME_RESOLUTION_AVAILABLE
692+
// If the server endpoint is invalid, it means whatever the user entered in the address
693+
// field was not an IP address, and must be presumed to be a hostname.
694+
if (serverEndpoint.Family == NetworkFamily.Invalid)
695+
{
696+
return m_Driver.Connect(ConnectionData.Address, ConnectionData.Port);
697+
}
698+
#endif
740699
return m_Driver.Connect(serverEndpoint);
741700
}
742701

743702
private bool ServerBindAndListen(NetworkEndpoint endPoint)
744703
{
745-
// Verify the endpoint is valid before proceeding
746704
if (endPoint.Family == NetworkFamily.Invalid)
747705
{
748-
#if HOSTNAME_RESOLUTION_AVAILABLE && UTP_TRANSPORT_2_4_ABOVE
749-
// If it's not valid, assure it meets FQDN standards
750-
if (!IsValidFqdn(ConnectionData.Address))
751-
{
752-
// If not then log an error and return false
753-
Debug.LogError($"Listen network address ({ConnectionData.Address}) is not a valid {NetworkFamily.Ipv4} or {NetworkFamily.Ipv6} address!");
754-
}
755-
else
756-
{
757-
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!");
758-
}
759706
return false;
760-
#else
761-
Debug.LogError($"Network listen address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
762-
return false;
763-
#endif
764707
}
765708

766709
InitDriver();

com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,6 @@
6868
"expression": "6000.0.11f1",
6969
"define": "COM_UNITY_MODULES_PHYSICS2D_LINEAR"
7070
},
71-
{
72-
"name": "com.unity.transport",
73-
"expression": "2.4.0",
74-
"define": "UTP_TRANSPORT_2_4_ABOVE"
75-
},
7671
{
7772
"name": "Unity",
7873
"expression": "6000.1.0a1",

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

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,11 @@ public void UnityTransport_RestartSucceedsAfterFailure()
127127
UnityTransport transport = new GameObject().AddComponent<UnityTransport>();
128128
transport.Initialize();
129129

130-
transport.SetConnectionData("127.0.0.", 4242, "127.0.0.");
130+
transport.SetConnectionData("127.0.0.1", 4242, "foobar");
131131

132132
Assert.False(transport.StartServer());
133-
LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242.");
133+
LogAssert.Expect(LogType.Error, "Invalid listen endpoint: foobar:4242. Note that the listen endpoint MUST be an IP address (not a hostname).");
134134

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
140135
transport.SetConnectionData("127.0.0.1", 4242, "127.0.0.1");
141136
Assert.True(transport.StartServer());
142137

@@ -156,24 +151,6 @@ public void UnityTransport_StartServerWithoutAddresses()
156151
transport.Shutdown();
157152
}
158153

159-
// Check that StartClient returns false with bad connection data.
160-
[Test]
161-
public void UnityTransport_StartClientFailsWithBadAddress()
162-
{
163-
UnityTransport transport = new GameObject().AddComponent<UnityTransport>();
164-
transport.Initialize();
165-
166-
transport.SetConnectionData("foobar", 4242);
167-
Assert.False(transport.StartClient());
168-
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
172-
LogAssert.Expect(LogType.Error, "Target server network address (foobar) is Invalid!");
173-
#endif
174-
transport.Shutdown();
175-
}
176-
177154
[Test]
178155
public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] string cert, [Values("", null)] string secret)
179156
{
@@ -206,5 +183,37 @@ public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] st
206183
}
207184
}
208185
}
186+
187+
#if HOSTNAME_RESOLUTION_AVAILABLE
188+
private static readonly (string, bool)[] k_HostnameChecks =
189+
{
190+
("localhost", true),
191+
("unity3d.com", true),
192+
("unity3d.com.", true),
193+
(string.Empty, false),
194+
("unity3d.com/test", false),
195+
("test%123.com", false),
196+
};
197+
198+
[Test]
199+
[TestCaseSource(nameof(k_HostnameChecks))]
200+
public void UnityTransport_HostnameValidation((string, bool) testCase)
201+
{
202+
var (hostname, isValid) = testCase;
203+
204+
UnityTransport transport = new GameObject().AddComponent<UnityTransport>();
205+
transport.Initialize();
206+
207+
if (!isValid)
208+
{
209+
LogAssert.Expect(LogType.Error, $"Provided connection address \"{hostname}\" is not a valid hostname.");
210+
}
211+
212+
transport.SetConnectionData(hostname, 4242);
213+
Assert.AreEqual(isValid, transport.StartClient());
214+
215+
transport.Shutdown();
216+
}
217+
#endif
209218
}
210219
}

com.unity.netcode.gameobjects/Tests/Editor/Unity.Netcode.Editor.Tests.asmdef

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@
3838
"expression": "(0,2022.2.0a5)",
3939
"define": "UNITY_UNET_PRESENT"
4040
},
41-
{
42-
"name": "com.unity.transport",
43-
"expression": "2.4.0",
44-
"define": "UTP_TRANSPORT_2_4_ABOVE"
45-
},
4641
{
4742
"name": "Unity",
4843
"expression": "6000.1.0a1",

0 commit comments

Comments
 (0)