From 7c724aaa56c780b0e9e742a038696083691d9581 Mon Sep 17 00:00:00 2001 From: mischa Date: Tue, 16 Jul 2024 10:01:43 +0200 Subject: [PATCH] EntityStateMessageUnreliable with custom handling --- Assets/Mirror/Core/Messages.cs | 10 ++++ Assets/Mirror/Core/NetworkClient.cs | 16 ++++++- Assets/Mirror/Core/NetworkIdentity.cs | 7 +-- Assets/Mirror/Core/NetworkServer.cs | 46 ++++++++++++++++++- .../Mirror/Examples/Tanks/Prefabs/Tank.prefab | 6 +-- 5 files changed, 76 insertions(+), 9 deletions(-) diff --git a/Assets/Mirror/Core/Messages.cs b/Assets/Mirror/Core/Messages.cs index 62888c07e..c64605e09 100644 --- a/Assets/Mirror/Core/Messages.cs +++ b/Assets/Mirror/Core/Messages.cs @@ -94,6 +94,7 @@ public struct ObjectHideMessage : NetworkMessage public uint netId; } + // state update for Traditional reliable sync public struct EntityStateMessage : NetworkMessage { public uint netId; @@ -102,6 +103,15 @@ public struct EntityStateMessage : NetworkMessage public ArraySegment payload; } + // state update for FastPaced unreliable sync + public struct EntityStateMessageUnreliable : NetworkMessage + { + public uint netId; + // the serialized component data + // -> ArraySegment to avoid unnecessary allocations + public ArraySegment payload; + } + // whoever wants to measure rtt, sends this to the other end. public struct NetworkPingMessage : NetworkMessage { diff --git a/Assets/Mirror/Core/NetworkClient.cs b/Assets/Mirror/Core/NetworkClient.cs index 7b7b57167..db1977b66 100644 --- a/Assets/Mirror/Core/NetworkClient.cs +++ b/Assets/Mirror/Core/NetworkClient.cs @@ -505,6 +505,7 @@ internal static void RegisterMessageHandlers(bool hostMode) RegisterHandler(_ => { }); // host mode doesn't need state updates RegisterHandler(_ => { }); + RegisterHandler(_ => { }); } else { @@ -516,6 +517,7 @@ internal static void RegisterMessageHandlers(bool hostMode) RegisterHandler(OnObjectSpawnStarted); RegisterHandler(OnObjectSpawnFinished); RegisterHandler(OnEntityStateMessage); + RegisterHandler(OnEntityStateMessageUnreliable); } // These handlers are the same for host and remote clients @@ -1393,6 +1395,18 @@ static void OnEntityStateMessage(EntityStateMessage message) else Debug.LogWarning($"Did not find target for sync message for {message.netId}. Were all prefabs added to the NetworkManager's spawnable list?\nNote: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message."); } + static void OnEntityStateMessageUnreliable(EntityStateMessageUnreliable message) + { + // Debug.Log($"NetworkClient.OnUpdateVarsMessage {msg.netId}"); + if (spawned.TryGetValue(message.netId, out NetworkIdentity identity) && identity != null) + { + // iniital is always 'true' because unreliable state sync alwasy serializes full + using (NetworkReaderPooled reader = NetworkReaderPool.Get(message.payload)) + identity.DeserializeClient(reader, true); + } + else Debug.LogWarning($"Did not find target for sync message for {message.netId}. Were all prefabs added to the NetworkManager's spawnable list?\nNote: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message."); + } + static void OnRPCMessage(RpcMessage message) { // Debug.Log($"NetworkClient.OnRPCMessage hash:{message.functionHash} netId:{message.netId}"); @@ -1567,7 +1581,7 @@ static void Broadcast() if (writer.Position > 0) { // send state update message - EntityStateMessage message = new EntityStateMessage + EntityStateMessageUnreliable message = new EntityStateMessageUnreliable { netId = identity.netId, payload = writer.ToArraySegment() diff --git a/Assets/Mirror/Core/NetworkIdentity.cs b/Assets/Mirror/Core/NetworkIdentity.cs index 26e9605b6..06bcad737 100644 --- a/Assets/Mirror/Core/NetworkIdentity.cs +++ b/Assets/Mirror/Core/NetworkIdentity.cs @@ -1090,8 +1090,9 @@ internal void SerializeClient(SyncMethod method, NetworkWriter writer) } // deserialize components from the client on the server. - // there's no 'initialState'. server always knows the initial state. - internal bool DeserializeServer(NetworkReader reader) + // for reliable state sync, server always knows the initial state. + // for unreliable, we always sync full state so we still need the parameter. + internal bool DeserializeServer(NetworkReader reader, bool initialState) { // ensure NetworkBehaviours are valid before usage ValidateComponents(); @@ -1115,7 +1116,7 @@ internal bool DeserializeServer(NetworkReader reader) // deserialize this component // server always knows the initial state (initial=false) // disconnect if failed, to prevent exploits etc. - if (!comp.Deserialize(reader, false)) return false; + if (!comp.Deserialize(reader, initialState)) return false; // server received state from the owner client. // set dirty so it's broadcast to other clients too. diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs index 072dea133..ac8278130 100644 --- a/Assets/Mirror/Core/NetworkServer.cs +++ b/Assets/Mirror/Core/NetworkServer.cs @@ -296,6 +296,7 @@ internal static void RegisterMessageHandlers() RegisterHandler(NetworkTime.OnServerPing, false); RegisterHandler(NetworkTime.OnServerPong, false); RegisterHandler(OnEntityStateMessage, true); + RegisterHandler(OnEntityStateMessageUnreliable, true); RegisterHandler(OnTimeSnapshotMessage, true); } @@ -384,7 +385,8 @@ static void OnEntityStateMessage(NetworkConnectionToClient connection, EntitySta { // DeserializeServer checks permissions internally. // failure to deserialize disconnects to prevent exploits. - if (!identity.DeserializeServer(reader)) + // for reliable sync, server always knows initial state so updates are initialState=false. + if (!identity.DeserializeServer(reader, false)) { if (exceptionsDisconnect) { @@ -407,6 +409,46 @@ static void OnEntityStateMessage(NetworkConnectionToClient connection, EntitySta // else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message."); } + // for client's owned ClientToServer components. + static void OnEntityStateMessageUnreliable(NetworkConnectionToClient connection, EntityStateMessageUnreliable message) + { + // need to validate permissions carefully. + // an attacker may attempt to modify a not-owned or not-ClientToServer component. + + // valid netId? + if (spawned.TryGetValue(message.netId, out NetworkIdentity identity) && identity != null) + { + // owned by the connection? + if (identity.connectionToClient == connection) + { + using (NetworkReaderPooled reader = NetworkReaderPool.Get(message.payload)) + { + // DeserializeServer checks permissions internally. + // failure to deserialize disconnects to prevent exploits. + // for unreliable sync, we always send full state (initialState=true). + if (!identity.DeserializeServer(reader, true)) + { + if (exceptionsDisconnect) + { + Debug.LogError($"Server failed to deserialize client unreliable state for {identity.name} with netId={identity.netId}, Disconnecting."); + connection.Disconnect(); + } + else + Debug.LogWarning($"Server failed to deserialize client unreliable state for {identity.name} with netId={identity.netId}."); + } + } + } + // An attacker may attempt to modify another connection's entity + // This could also be a race condition of message in flight when + // RemoveClientAuthority is called, so not malicious. + // Don't disconnect, just log the warning. + else + Debug.LogWarning($"EntityStateMessage from {connection} for {identity.name} without authority."); + } + // no warning. don't spam server logs. + // else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message."); + } + // client sends TimeSnapshotMessage every sendInterval. // batching already includes the remoteTimestamp. // we simply insert it on-message here. @@ -1916,7 +1958,7 @@ static void BroadcastToConnection(NetworkConnectionToClient connection) serialization = SerializeForConnection(identity, connection, SyncMethod.FastPaced); if (serialization != null) { - EntityStateMessage message = new EntityStateMessage + EntityStateMessageUnreliable message = new EntityStateMessageUnreliable { netId = identity.netId, payload = serialization.ToArraySegment() diff --git a/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab index 0dedb673a..1f7844691 100644 --- a/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab +++ b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab @@ -67,7 +67,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d993bac37a92145448c1ea027b3e9ddc, type: 3} m_Name: m_EditorClassIdentifier: - syncMethod: 0 + syncMethod: 1 syncDirection: 1 syncMode: 0 syncInterval: 0 @@ -99,7 +99,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d993bac37a92145448c1ea027b3e9ddc, type: 3} m_Name: m_EditorClassIdentifier: - syncMethod: 0 + syncMethod: 1 syncDirection: 1 syncMode: 0 syncInterval: 0 @@ -131,7 +131,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7deadf756194d461e9140e42d651693b, type: 3} m_Name: m_EditorClassIdentifier: - syncMethod: 0 + syncMethod: 1 syncDirection: 0 syncMode: 0 syncInterval: 0.1