diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 2c5d220a40..2eb3776c3b 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -10,6 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Added
+- It is now possible to control which port clients will bind to using the `UnityTransport.ConnectionData.ClientBindPort` field. If not set, clients will bind to an ephemeral port (same as before this change). (#3764)
+
### Changed
diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs
index 5b71fcadbe..2f3c987ab2 100644
--- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs
@@ -245,6 +245,12 @@ internal static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
return endpoint;
}
+ ///
+ /// The port the client will bind to. If 0 (the default), an ephemeral port will be used.
+ ///
+ [SerializeField]
+ public ushort ClientBindPort;
+
///
/// Endpoint (IP address and port) clients will connect to.
///
@@ -683,6 +689,20 @@ private bool ClientBindAndConnect()
}
InitDriver();
+
+ // Don't bind yet if connecting to a hostname, since we don't know if it will resolve to IPv4 or IPv6.
+ if (serverEndpoint.Family != NetworkFamily.Invalid && ConnectionData.ClientBindPort != 0)
+ {
+ var bindEndpoint = serverEndpoint.Family == NetworkFamily.Ipv6
+ ? NetworkEndpoint.AnyIpv6.WithPort(ConnectionData.ClientBindPort)
+ : NetworkEndpoint.AnyIpv4.WithPort(ConnectionData.ClientBindPort);
+ if (m_Driver.Bind(bindEndpoint) != 0)
+ {
+ Debug.LogError($"Couldn't create socket. Possibly another process is using port {ConnectionData.ClientBindPort}.");
+ return false;
+ }
+ }
+
Connect(serverEndpoint);
return true;
@@ -788,16 +808,17 @@ public void SetClientRelayData(string ipAddress, ushort port, byte[] allocationI
///
/// 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 or a domain name)
- /// The remote port
- /// The local listen address
+ /// The remote IP address (despite the name, can be an IPv6 address or a domain name).
+ /// The remote port to connect to.
+ /// The address the server is going to listen on.
public void SetConnectionData(string ipv4Address, ushort port, string listenAddress = null)
{
ConnectionData = new ConnectionAddressData
{
Address = ipv4Address,
Port = port,
- ServerListenAddress = listenAddress ?? ipv4Address
+ ServerListenAddress = listenAddress ?? ipv4Address,
+ ClientBindPort = ConnectionData.ClientBindPort
};
SetProtocol(ProtocolType.UnityTransport);
@@ -806,8 +827,8 @@ public void SetConnectionData(string ipv4Address, ushort port, string listenAddr
///
/// Sets IP and Port information. This will be ignored if using the Unity Relay and you should call
///
- /// The remote end point
- /// The local listen endpoint
+ /// The remote endpoint the client should connect to.
+ /// The endpoint the server should listen on.
public void SetConnectionData(NetworkEndpoint endPoint, NetworkEndpoint listenEndPoint = default)
{
string serverAddress = endPoint.Address.Split(':')[0];
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs
index 7afd0b879d..594467efc2 100644
--- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs
@@ -184,6 +184,20 @@ public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] st
}
}
+ [Test]
+ public void UnityTransport_BindClientToSpecificPort()
+ {
+ UnityTransport transport = new GameObject().AddComponent();
+ transport.Initialize();
+ transport.SetConnectionData("127.0.0.1", 4242);
+ transport.ConnectionData.ClientBindPort = 14242;
+
+ Assert.True(transport.StartClient());
+ Assert.AreEqual(14242, transport.GetLocalEndpoint().Port);
+
+ transport.Shutdown();
+ }
+
#if HOSTNAME_RESOLUTION_AVAILABLE
private static readonly (string, bool)[] k_HostnameChecks =
{
diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json
index 1942f6f429..c0d0083726 100644
--- a/com.unity.netcode.gameobjects/package.json
+++ b/com.unity.netcode.gameobjects/package.json
@@ -2,7 +2,7 @@
"name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
- "version": "2.7.1",
+ "version": "2.8.0",
"unity": "6000.0",
"dependencies": {
"com.unity.nuget.mono-cecil": "1.11.4",