Skip to content

Commit 5aa2c63

Browse files
fix: Accept single words as valid hostnames
1 parent 2551c15 commit 5aa2c63

File tree

4 files changed

+61
-142
lines changed

4 files changed

+61
-142
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1414
### Fixed
1515

16+
- Fixed an issue where `UnityTransport` would not accept single words as valid hostnames (notably "localhost").
1617
- 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)
1718
- 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)
1819

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

Lines changed: 23 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ public struct ConnectionAddressData
235235
[SerializeField]
236236
public string ServerListenAddress;
237237

238-
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
238+
internal static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
239239
{
240240
NetworkEndpoint endpoint = default;
241241
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4))
@@ -245,11 +245,6 @@ private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
245245
return endpoint;
246246
}
247247

248-
private void InvalidEndpointError()
249-
{
250-
Debug.LogError($"Invalid network endpoint: {Address}:{Port}.");
251-
}
252-
253248
/// <summary>
254249
/// Endpoint (IP address and port) clients will connect to.
255250
/// </summary>
@@ -259,23 +254,7 @@ private void InvalidEndpointError()
259254
/// discouraged.
260255
/// </remarks>
261256
[Obsolete("Use NetworkEndpoint.Parse on the Address field instead.")]
262-
public NetworkEndpoint ServerEndPoint
263-
{
264-
get
265-
{
266-
var networkEndpoint = ParseNetworkEndpoint(Address, Port);
267-
if (networkEndpoint == default)
268-
{
269-
#if HOSTNAME_RESOLUTION_AVAILABLE
270-
if (!IsValidFqdn(Address))
271-
#endif
272-
{
273-
InvalidEndpointError();
274-
}
275-
}
276-
return networkEndpoint;
277-
}
278-
}
257+
public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port);
279258

280259
/// <summary>
281260
/// Endpoint (IP address and port) server will listen/bind on.
@@ -287,32 +266,27 @@ public NetworkEndpoint ListenEndPoint
287266
NetworkEndpoint endpoint = default;
288267
if (string.IsNullOrEmpty(ServerListenAddress))
289268
{
290-
endpoint = NetworkEndpoint.LoopbackIpv4;
291-
292-
// If an address was entered and it's IPv6, switch to using ::1 as the
293-
// default listen address. (Otherwise we always assume IPv4.)
294-
if (!string.IsNullOrEmpty(Address) && ServerEndPoint.Family == NetworkFamily.Ipv6)
295-
{
296-
endpoint = NetworkEndpoint.LoopbackIpv6;
297-
}
269+
endpoint = IsIpv6 ? NetworkEndpoint.LoopbackIpv6 : NetworkEndpoint.LoopbackIpv4;
298270
endpoint = endpoint.WithPort(Port);
299271
}
300272
else
301273
{
302274
endpoint = ParseNetworkEndpoint(ServerListenAddress, Port);
303275
if (endpoint == default)
304276
{
305-
InvalidEndpointError();
277+
Debug.LogError($"Invalid listen endpoint: {ServerListenAddress}:{Port}. Note that the listen endpoint MUST be an IP address (not a hostname).");
306278
}
307279
}
308280
return endpoint;
309281
}
310282
}
311283

312284
/// <summary>
313-
/// Returns true if the end point address is of type <see cref="NetworkFamily.Ipv6"/>.
285+
/// Returns true if the end point address is of type <see cref="NetworkFamily.Ipv6"/> or
286+
/// if it is a hostname (because in current versions of the engine, hostname resolution
287+
/// prioritizes IPv6 addresses).
314288
/// </summary>
315-
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv6);
289+
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && !NetworkEndpoint.TryParse(Address, Port, out NetworkEndpoint _, NetworkFamily.Ipv4);
316290
}
317291

318292

@@ -673,16 +647,6 @@ private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery)
673647
}
674648
}
675649

676-
#if HOSTNAME_RESOLUTION_AVAILABLE
677-
private static bool IsValidFqdn(string fqdn)
678-
{
679-
// Regular expression to validate FQDN
680-
string pattern = @"^(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.(?!-)(?:[A-Za-z0-9-]{1,63}\.?)+[A-Za-z]{2,6}$";
681-
var regex = new Regex(pattern);
682-
return regex.IsMatch(fqdn);
683-
}
684-
#endif
685-
686650
private bool ClientBindAndConnect()
687651
{
688652
var serverEndpoint = default(NetworkEndpoint);
@@ -693,44 +657,20 @@ private bool ClientBindAndConnect()
693657
}
694658
else
695659
{
696-
serverEndpoint = ConnectionData.ServerEndPoint;
697-
}
698-
699-
// Verify the endpoint is valid before proceeding
700-
if (serverEndpoint.Family == NetworkFamily.Invalid)
701-
{
702-
#if HOSTNAME_RESOLUTION_AVAILABLE
703-
704-
// If it's not valid, assure it meets FQDN standards
705-
if (IsValidFqdn(ConnectionData.Address))
706-
{
707-
// If so, then proceed with driver initialization and attempt to connect
708-
InitDriver();
709-
m_Driver.Connect(ConnectionData.Address, ConnectionData.Port);
710-
return true;
711-
}
712-
else
660+
// This will result in an invalid endpoint if the address is a hostname.
661+
// This is handled later in the Connect method if hostname resolution is available,
662+
// but if not then we need to error out here.
663+
serverEndpoint = ConnectionAddressData.ParseNetworkEndpoint(ConnectionData.Address, ConnectionData.Port);
664+
#if !HOSTNAME_RESOLUTION_AVAILABLE
665+
if (serverEndpoint.Family == NetworkFamily.Invalid)
713666
{
714-
// If not then log an error and return false
715-
Debug.LogError($"Target server network address ({ConnectionData.Address}) is not a valid Fully Qualified Domain Name!");
667+
Debug.LogError($"Invalid server address: {ConnectionData.Address}:{ConnectionData.Port}.");
716668
return false;
717669
}
718-
#else
719-
Debug.LogError($"Target server network address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
720-
return false;
721670
#endif
722671
}
723672

724673
InitDriver();
725-
726-
var bindEndpoint = serverEndpoint.Family == NetworkFamily.Ipv6 ? NetworkEndpoint.AnyIpv6 : NetworkEndpoint.AnyIpv4;
727-
int result = m_Driver.Bind(bindEndpoint);
728-
if (result != 0)
729-
{
730-
Debug.LogError("Client failed to bind");
731-
return false;
732-
}
733-
734674
Connect(serverEndpoint);
735675

736676
return true;
@@ -743,30 +683,22 @@ private bool ClientBindAndConnect()
743683
/// <returns>A <see cref="NetworkConnection"/> representing the connection to the server, or an invalid connection if the connection attempt fails.</returns>
744684
protected virtual NetworkConnection Connect(NetworkEndpoint serverEndpoint)
745685
{
686+
#if HOSTNAME_RESOLUTION_AVAILABLE
687+
// If the server endpoint is invalid, it means whatever the user entered in the address
688+
// field was not an IP address, and must be presumed to be a hostname.
689+
if (serverEndpoint.Family == NetworkFamily.Invalid)
690+
{
691+
return m_Driver.Connect(ConnectionData.Address, ConnectionData.Port);
692+
}
693+
#endif
746694
return m_Driver.Connect(serverEndpoint);
747695
}
748696

749697
private bool ServerBindAndListen(NetworkEndpoint endPoint)
750698
{
751-
// Verify the endpoint is valid before proceeding
752699
if (endPoint.Family == NetworkFamily.Invalid)
753700
{
754-
#if HOSTNAME_RESOLUTION_AVAILABLE
755-
// If it's not valid, assure it meets FQDN standards
756-
if (!IsValidFqdn(ConnectionData.Address))
757-
{
758-
// If not then log an error and return false
759-
Debug.LogError($"Listen network address ({ConnectionData.Address}) is not a valid {NetworkFamily.Ipv4} or {NetworkFamily.Ipv6} address!");
760-
}
761-
else
762-
{
763-
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!");
764-
}
765701
return false;
766-
#else
767-
Debug.LogError($"Network listen address ({ConnectionData.Address}) is {nameof(NetworkFamily.Invalid)}!");
768-
return false;
769-
#endif
770702
}
771703

772704
InitDriver();

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

Lines changed: 2 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
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
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
{

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

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NUnit.Framework;
66
using Unity.Netcode.TestHelpers.Runtime;
77
using Unity.Netcode.Transports.UTP;
8+
using Unity.Networking.Transport;
89
using UnityEngine;
910
using UnityEngine.TestTools;
1011
using static Unity.Netcode.RuntimeTests.UnityTransportTestHelpers;
@@ -55,45 +56,52 @@ public IEnumerator Cleanup()
5556
yield return null;
5657
}
5758

58-
// Check that invalid endpoint addresses are detected and return false if detected
59-
[Test]
60-
public void DetectInvalidEndpoint()
59+
// Check connection with a single client (IP address).
60+
[UnityTest]
61+
public IEnumerator ConnectSingleClient_IPAddress()
6162
{
62-
using var netcodeLogAssert = new NetcodeLogAssert(true);
6363
InitializeTransport(out m_Server, out m_ServerEvents);
6464
InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]);
65-
m_Server.ConnectionData.Address = "Fubar";
66-
m_Server.ConnectionData.ServerListenAddress = "Fubar";
67-
m_Clients[0].ConnectionData.Address = "MoreFubar";
68-
Assert.False(m_Server.StartServer(), "Server failed to detect invalid endpoint!");
69-
Assert.False(m_Clients[0].StartClient(), "Client failed to detect invalid endpoint!");
70-
#if HOSTNAME_RESOLUTION_AVAILABLE
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
80-
netcodeLogAssert.LogWasReceived(LogType.Error, $"Network listen address ({m_Server.ConnectionData.Address}) is Invalid!");
81-
netcodeLogAssert.LogWasReceived(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is Invalid!");
82-
#endif
8365

84-
UnityTransportTestComponent.CleanUp();
66+
m_Clients[0].SetConnectionData("127.0.0.1", 7777);
67+
68+
m_Server.StartServer();
69+
m_Clients[0].StartClient();
70+
71+
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[0]);
72+
73+
// Check we've received Connect event on server too.
74+
Assert.AreEqual(1, m_ServerEvents.Count);
75+
Assert.AreEqual(NetworkEvent.Connect, m_ServerEvents[0].Type);
76+
77+
yield return null;
8578
}
8679

87-
// Check connection with a single client.
80+
#if HOSTNAME_RESOLUTION_AVAILABLE
81+
// Check connection with a single client (hostname).
8882
[UnityTest]
89-
public IEnumerator ConnectSingleClient()
83+
public IEnumerator ConnectSingleClient_Hostname()
9084
{
9185
InitializeTransport(out m_Server, out m_ServerEvents);
9286
InitializeTransport(out m_Clients[0], out m_ClientsEvents[0]);
9387

94-
m_Server.StartServer();
88+
// We don't know if localhost will resolve to 127.0.0.1 or ::1, so we wait until we know
89+
// before starting the server. Because localhost is pretty much always defined locally
90+
// it should resolve immediatly and thus waiting one frame should be enough.
91+
92+
// We'll need to retry connection requests most likely so make this fast.
93+
m_Clients[0].ConnectTimeoutMS = 50;
94+
95+
m_Clients[0].SetConnectionData("localhost", 7777);
9596
m_Clients[0].StartClient();
9697

98+
yield return null;
99+
100+
var endpoint = m_Clients[0].GetLocalEndpoint();
101+
var ip = endpoint.Family == NetworkFamily.Ipv4 ? "127.0.0.1" : "::1";
102+
m_Server.SetConnectionData(ip, 7777, ip);
103+
m_Server.StartServer();
104+
97105
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[0]);
98106

99107
// Check we've received Connect event on server too.
@@ -102,6 +110,7 @@ public IEnumerator ConnectSingleClient()
102110

103111
yield return null;
104112
}
113+
#endif
105114

106115
// Check connection with multiple clients.
107116
[UnityTest]

0 commit comments

Comments
 (0)