Unity-Technologies / multiplayer-community-contributions

Community contributions to Unity Multiplayer Networking products and services.
MIT License
421 stars 161 forks source link

Websocket Connect to URL instead of IP #196

Open DevDavey opened 1 year ago

DevDavey commented 1 year ago

I'm having trouble configuring websockets using a URL instead of an IP address. It works fine when using the IP address and I've confirmed the port is open and the websocket server is accepting incomming connections. (Replaced my real URL with example.io just for this post)

Maybe this is something that is not currently supported but I can't imagine it would be too difficult to implement.

Invalid network endpoint: example.io:7777.
UnityEngine.Debug:LogError (object)
Unity.Netcode.Transports.UTP.UnityTransport/ConnectionAddressData:ParseNetworkEndpoint (string,uint16) (at Library/PackageCache/com.unity.netcode.gameobjects@1.2.0/Runtime/Transports/UTP/UnityTransport.cs:319)
Unity.Netcode.Transports.UTP.UnityTransport/ConnectionAddressData:get_ServerEndPoint () (at Library/PackageCache/com.unity.netcode.gameobjects@1.2.0/Runtime/Transports/UTP/UnityTransport.cs:328)
Unity.Netcode.Transports.UTP.UnityTransport:ClientBindAndConnect () (at Library/PackageCache/com.unity.netcode.gameobjects@1.2.0/Runtime/Transports/UTP/UnityTransport.cs:506)
Unity.Netcode.Transports.UTP.UnityTransport:StartClient () (at Library/PackageCache/com.unity.netcode.gameobjects@1.2.0/Runtime/Transports/UTP/UnityTransport.cs:1282)
Unity.Netcode.NetworkManager:StartClient () (at Library/PackageCache/com.unity.netcode.gameobjects@1.2.0/Runtime/Core/NetworkManager.cs:1104)
Unity.Netcode.Samples.BootstrapManager:OnGUI () (at Assets/Samples/Netcode for GameObjects/1.2.0/Bootstrap/Scripts/BootstrapManager.cs:26)

This is the function throwing the exception.

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))
                {
                    Debug.LogError($"Invalid network endpoint: {ip}:{port}.");
                }

                return endpoint;
            }

image

DevDavey commented 1 year ago

Connection works when I use SSL Encryption and set the URL as ServerCommonName 👍

var networkManager = NetworkManager.Singleton;
m_Transport = (UnityTransport)m_NetworkManager.NetworkConfig.NetworkTransport;
m_Transport.SetServerSecrets(MyGameServerCertificate, MyGameServerPrivateKey);
networkManager.StartServer();
m_Transport.SetClientSecrets(ServerCommonName, MyGameClientCA);
networkManager.StartClient();

However, now I am running into a new issue.

The first client connects perfectly fine, but when a second client trys to connect the first client is kicked from the game and the second client recieves a failed to connect error. When the second client attempts to connect again it works the second time.

Server shows this error:

TLS handshake failed at step 8. Closing connection.
0x00007ff792cfe58a (Unity) DefaultBurstRuntimeLogCallback
0x00007ff7923f17ba (Unity) BurstCompilerService_CUSTOM_RuntimeLog
0x00007ffbfdbeb472 (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.TLSLayer/ReceiveJob::Unity.Networking.Transport.TLSLayer.ReceiveJob.CheckForFailedClient (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/TLSLayer.cs:417)
0x00007ffbfdbecf9a (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.TLSLayer/ReceiveJob::Unity.Networking.Transport.TLSLayer.ReceiveJob.ProcessConnectionList (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/TLSLayer.cs:321)
0x00007ffbfdbea101 (c5d232c9cae857190c1d09f2492afd3) a3be22cf2efbecd470a3a9efab4602bf
0x00007ff792fda620 (Unity) ExecuteJob
0x00007ff792fdb9ff (Unity) ForwardJobToManaged
0x00007ff792fd82b4 (Unity) ujob_execute_job
0x00007ff792fd79c7 (Unity) lane_guts
0x00007ff792fd9874 (Unity) worker_thread_routine
0x00007ff7931f0d97 (Unity) Thread::RunThreadWrapper
0x00007ffc35977614 (KERNEL32) BaseThreadInitThunk
0x00007ffc373e26a1 (ntdll) RtlUserThreadStart

And these warnings:

Received message with invalid reserved header bits
0x00007ff792cfe58a (Unity) DefaultBurstRuntimeLogCallback
0x00007ff7923f17ba (Unity) BurstCompilerService_CUSTOM_RuntimeLog
0x00007ffbfdbbb9d0 (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.WebSocketLayer/ReceiveJob::Unity.Networking.Transport.WebSocketLayer.ReceiveJob.ProcessWebSocketFrames (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/WebSocketLayer.cs:586)
0x00007ffbfdbba689 (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.WebSocketLayer/ReceiveJob::Unity.Networking.Transport.WebSocketLayer.ReceiveJob.ProcessReceivedMessages (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/WebSocketLayer.cs:525)
0x00007ffbfdbadb2d (c5d232c9cae857190c1d09f2492afd3) Unity.Jobs.IJobExtensions.JobStruct`1<Unity.Networking.Transport.WebSocketLayer.ReceiveJob>.Execute (at C:/Users/Davey/Documents/OffTheRails-v2/unknown/unknown:0)
0x00007ffbfdba9e31 (c5d232c9cae857190c1d09f2492afd3) ce7051e47e4dcf1c405534050a172262
0x00007ff792fda620 (Unity) ExecuteJob
0x00007ff792fdb9ff (Unity) ForwardJobToManaged
0x00007ff792fd82b4 (Unity) ujob_execute_job
0x00007ff792fd79c7 (Unity) lane_guts
0x00007ff792fd9874 (Unity) worker_thread_routine
0x00007ff7931f0d97 (Unity) Thread::RunThreadWrapper
0x00007ffc35977614 (KERNEL32) BaseThreadInitThunk
0x00007ffc373e26a1 (ntdll) RtlUserThreadStart
Received message with unexpected masking
0x00007ff792cfe58a (Unity) DefaultBurstRuntimeLogCallback
0x00007ff7923f17ba (Unity) BurstCompilerService_CUSTOM_RuntimeLog
0x00007ffbfdbbba0a (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.WebSocketLayer/ReceiveJob::Unity.Networking.Transport.WebSocketLayer.ReceiveJob.ProcessWebSocketFrames (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/WebSocketLayer.cs:587)
0x00007ffbfdbba689 (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.WebSocketLayer/ReceiveJob::Unity.Networking.Transport.WebSocketLayer.ReceiveJob.ProcessReceivedMessages (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/WebSocketLayer.cs:525)
0x00007ffbfdbadb2d (c5d232c9cae857190c1d09f2492afd3) Unity.Jobs.IJobExtensions.JobStruct`1<Unity.Networking.Transport.WebSocketLayer.ReceiveJob>.Execute (at C:/Users/Davey/Documents/OffTheRails-v2/unknown/unknown:0)
0x00007ffbfdba9e31 (c5d232c9cae857190c1d09f2492afd3) ce7051e47e4dcf1c405534050a172262
0x00007ff792fda620 (Unity) ExecuteJob
0x00007ff792fdb9ff (Unity) ForwardJobToManaged
0x00007ff792fd82b4 (Unity) ujob_execute_job
0x00007ff792fd79c7 (Unity) lane_guts
0x00007ff792fd9874 (Unity) worker_thread_routine
0x00007ff7931f0d97 (Unity) Thread::RunThreadWrapper
0x00007ffc35977614 (KERNEL32) BaseThreadInitThunk
0x00007ffc373e26a1 (ntdll) RtlUserThreadStart
Received a fragmented control frame.
0x00007ff792cfe58a (Unity) DefaultBurstRuntimeLogCallback
0x00007ff7923f17ba (Unity) BurstCompilerService_CUSTOM_RuntimeLog
0x00007ffbfdbbba48 (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.WebSocketLayer/ReceiveJob::Unity.Networking.Transport.WebSocketLayer.ReceiveJob.ProcessWebSocketFrames (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/WebSocketLayer.cs:588)
0x00007ffbfdbba689 (c5d232c9cae857190c1d09f2492afd3) Unity.Networking.Transport.WebSocketLayer/ReceiveJob::Unity.Networking.Transport.WebSocketLayer.ReceiveJob.ProcessReceivedMessages (at C:/Users/Davey/Documents/OffTheRails-v2/Library/PackageCache/com.unity.transport@2.0.0-pre.3/Runtime/Layers/WebSocketLayer.cs:525)
0x00007ffbfdbadb2d (c5d232c9cae857190c1d09f2492afd3) Unity.Jobs.IJobExtensions.JobStruct`1<Unity.Networking.Transport.WebSocketLayer.ReceiveJob>.Execute (at C:/Users/Davey/Documents/OffTheRails-v2/unknown/unknown:0)
0x00007ffbfdba9e31 (c5d232c9cae857190c1d09f2492afd3) ce7051e47e4dcf1c405534050a172262
0x00007ff792fda620 (Unity) ExecuteJob
0x00007ff792fdb9ff (Unity) ForwardJobToManaged
0x00007ff792fd82b4 (Unity) ujob_execute_job
0x00007ff792fd79c7 (Unity) lane_guts
0x00007ff792fd9874 (Unity) worker_thread_routine
0x00007ff7931f0d97 (Unity) Thread::RunThreadWrapper
0x00007ffc35977614 (KERNEL32) BaseThreadInitThunk
0x00007ffc373e26a1 (ntdll) RtlUserThreadStart

I am using Unity Editor 2022.2.0b16 with Unity Transport 2.0.0-pre.3 and Websocket Transport 2.0.0 and NGO 1.2.0 The connections are being routed through a Cloudflare proxy. I've also tested with Unity Transport 2.0.0-exp.8 and run into the same issue. Running on port 443.

I find it weird that if the second client connecting recieves a TLS handshake error then why is the first client being kicked? Also this happens 95% of the time but there are a few occasions where both clients can connect simultaniously with no issue.

ToolKami commented 1 year ago

@Davey111 having the same issue, did you manage to progress?

DevDavey commented 1 year ago

@ToolKami Partially..

Instead of using the Unity Transport and checking the "Use Web Sockets" box I realized the package contains another transport script called "Web Socket Transport" that entirely replaces the default Unity Transport script.

This allows the use of URLs in the Address box but I still had the TLS handshake error.

My workaround was to run the server unencrypted and use an NGINX reverse SSL proxy to forward unencrypted packets to the server. Unfortuntely its not the prettiest fix but at least its working for me now.

This is how I have my client setup: image

Server setup: image

ToolKami commented 1 year ago

Thanks @Davey111 ! I'm fairly new to Unity, will try to spend a little more time to debug with my limited skills, otherwise I'll try your method, do you mind sharing your working nginx.conf too? 🙏

So far, I've traced the origin of the error TLS handshake failed at step 8. Closing connection. to com.unity.transport@2.0.0-exp.8/Runtime/DebugLog.cs:

public static void ErrorDTLSHandshakeFailed(uint handshakeStep)
        {
#if USE_UNITY_LOGGING
            Unity.Logging.Log.Error("DTLS handshake failed at step {HandshakeStep}. Closing connection.", handshakeStep);
#else
            UnityEngine.Debug.LogError($"DTLS handshake failed at step {handshakeStep}. Closing connection.");
#endif
        }

which is called from com.unitytransport@2.0.0-exp.8/Runtime/Layers/DTLSLayer.cs:

private void CheckForFailedClient(ConnectionId connection)
            {
                var clientPtr = ConnectionsData[connection].UnityTLSClientPtr;
                if (clientPtr == null)
                    return;

                ulong dummy;
                var errorState = Binding.unitytls_client_get_errorsState(clientPtr, &dummy);
                var clientState = Binding.unitytls_client_get_state(clientPtr);

                if (errorState != Binding.UNITYTLS_SUCCESS || clientState == Binding.UnityTLSClientState_Fail)
                {
                    // The only way to get a failed client is because of a failed handshake.
                    if (clientState == Binding.UnityTLSClientState_Fail)
                    {
                        // TODO Would be nice to translate the numerical step in a string.
                        var handshakeStep = Binding.unitytls_client_get_handshake_state(clientPtr);
                        DebugLog.ErrorDTLSHandshakeFailed(handshakeStep);
                    }

                    Connections.StartDisconnecting(ref connection);
                    // TODO Not the ideal disconnect reason. If we ever have a better one, use it.
                    Disconnect(connection, Error.DisconnectReason.ClosedByRemote);
                }
            }

I agree the TODO would be nice 😆

ToolKami commented 1 year ago

Was able to narrow it down in the Unity C# source UnityCsReference/External/unitytls/builds/CSharp/BindingsUnity/TLSAgent.gen.bindings.cs, but at a dead end without access to the C++ source:

        [FreeFunction(IsThreadSafe = true)]
        public static extern uint unitytls_client_get_handshake_state(unitytls_client* clientInstance);
        /// <summary>Retrieves the last error state reported by unitytls internally.</summary>
        /// <remarks>
        /// The return value is the unitytls_error_code value and the value written to reserved is the internal implmentation-specific value. In general, reserved is to be
        /// ignored in favor of the return value.
        /// If client is invalid and/and we would not retrieve these values UNITYTLS_USER_CUSTOM_ERROR_END is returned and
        /// reserved left untouched.
        /// </remarks>
        /// <param name="clientInstance">pointer to the (opaque) client instance</param>
        /// <param name="reserved">(out) pointer to an int where the implementation-specific error code value will be written</param>
DevDavey commented 1 year ago

Thanks @Davey111 ! I'm fairly new to Unity, will try to spend a little more time to debug with my limited skills, otherwise I'll try your method, do you mind sharing your working nginx.conf too? 🙏

Sure 😊

server {
    server_name example.com;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;

      proxy_pass http://localhost:7777;
      proxy_set_header  X-Real-IP $remote_addr;
      proxy_set_header  X-Real-Port $remote_port;

      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";

    }
}