EntityStateMessageUnreliable with custom handling

This commit is contained in:
mischa 2024-07-16 10:01:43 +02:00
parent cdb3f9fc55
commit 7c724aaa56
5 changed files with 76 additions and 9 deletions

View File

@ -94,6 +94,7 @@ public struct ObjectHideMessage : NetworkMessage
public uint netId; public uint netId;
} }
// state update for Traditional reliable sync
public struct EntityStateMessage : NetworkMessage public struct EntityStateMessage : NetworkMessage
{ {
public uint netId; public uint netId;
@ -102,6 +103,15 @@ public struct EntityStateMessage : NetworkMessage
public ArraySegment<byte> payload; public ArraySegment<byte> 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<byte> payload;
}
// whoever wants to measure rtt, sends this to the other end. // whoever wants to measure rtt, sends this to the other end.
public struct NetworkPingMessage : NetworkMessage public struct NetworkPingMessage : NetworkMessage
{ {

View File

@ -505,6 +505,7 @@ internal static void RegisterMessageHandlers(bool hostMode)
RegisterHandler<ObjectSpawnFinishedMessage>(_ => { }); RegisterHandler<ObjectSpawnFinishedMessage>(_ => { });
// host mode doesn't need state updates // host mode doesn't need state updates
RegisterHandler<EntityStateMessage>(_ => { }); RegisterHandler<EntityStateMessage>(_ => { });
RegisterHandler<EntityStateMessageUnreliable>(_ => { });
} }
else else
{ {
@ -516,6 +517,7 @@ internal static void RegisterMessageHandlers(bool hostMode)
RegisterHandler<ObjectSpawnStartedMessage>(OnObjectSpawnStarted); RegisterHandler<ObjectSpawnStartedMessage>(OnObjectSpawnStarted);
RegisterHandler<ObjectSpawnFinishedMessage>(OnObjectSpawnFinished); RegisterHandler<ObjectSpawnFinishedMessage>(OnObjectSpawnFinished);
RegisterHandler<EntityStateMessage>(OnEntityStateMessage); RegisterHandler<EntityStateMessage>(OnEntityStateMessage);
RegisterHandler<EntityStateMessageUnreliable>(OnEntityStateMessageUnreliable);
} }
// These handlers are the same for host and remote clients // 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."); 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) static void OnRPCMessage(RpcMessage message)
{ {
// Debug.Log($"NetworkClient.OnRPCMessage hash:{message.functionHash} netId:{message.netId}"); // Debug.Log($"NetworkClient.OnRPCMessage hash:{message.functionHash} netId:{message.netId}");
@ -1567,7 +1581,7 @@ static void Broadcast()
if (writer.Position > 0) if (writer.Position > 0)
{ {
// send state update message // send state update message
EntityStateMessage message = new EntityStateMessage EntityStateMessageUnreliable message = new EntityStateMessageUnreliable
{ {
netId = identity.netId, netId = identity.netId,
payload = writer.ToArraySegment() payload = writer.ToArraySegment()

View File

@ -1090,8 +1090,9 @@ internal void SerializeClient(SyncMethod method, NetworkWriter writer)
} }
// deserialize components from the client on the server. // deserialize components from the client on the server.
// there's no 'initialState'. server always knows the initial state. // for reliable state sync, server always knows the initial state.
internal bool DeserializeServer(NetworkReader reader) // 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 // ensure NetworkBehaviours are valid before usage
ValidateComponents(); ValidateComponents();
@ -1115,7 +1116,7 @@ internal bool DeserializeServer(NetworkReader reader)
// deserialize this component // deserialize this component
// server always knows the initial state (initial=false) // server always knows the initial state (initial=false)
// disconnect if failed, to prevent exploits etc. // 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. // server received state from the owner client.
// set dirty so it's broadcast to other clients too. // set dirty so it's broadcast to other clients too.

View File

@ -296,6 +296,7 @@ internal static void RegisterMessageHandlers()
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false); RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false);
RegisterHandler<NetworkPongMessage>(NetworkTime.OnServerPong, false); RegisterHandler<NetworkPongMessage>(NetworkTime.OnServerPong, false);
RegisterHandler<EntityStateMessage>(OnEntityStateMessage, true); RegisterHandler<EntityStateMessage>(OnEntityStateMessage, true);
RegisterHandler<EntityStateMessageUnreliable>(OnEntityStateMessageUnreliable, true);
RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage, true); RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage, true);
} }
@ -384,7 +385,8 @@ static void OnEntityStateMessage(NetworkConnectionToClient connection, EntitySta
{ {
// DeserializeServer checks permissions internally. // DeserializeServer checks permissions internally.
// failure to deserialize disconnects to prevent exploits. // 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) 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."); // 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. // client sends TimeSnapshotMessage every sendInterval.
// batching already includes the remoteTimestamp. // batching already includes the remoteTimestamp.
// we simply insert it on-message here. // we simply insert it on-message here.
@ -1916,7 +1958,7 @@ static void BroadcastToConnection(NetworkConnectionToClient connection)
serialization = SerializeForConnection(identity, connection, SyncMethod.FastPaced); serialization = SerializeForConnection(identity, connection, SyncMethod.FastPaced);
if (serialization != null) if (serialization != null)
{ {
EntityStateMessage message = new EntityStateMessage EntityStateMessageUnreliable message = new EntityStateMessageUnreliable
{ {
netId = identity.netId, netId = identity.netId,
payload = serialization.ToArraySegment() payload = serialization.ToArraySegment()

View File

@ -67,7 +67,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d993bac37a92145448c1ea027b3e9ddc, type: 3} m_Script: {fileID: 11500000, guid: d993bac37a92145448c1ea027b3e9ddc, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
syncMethod: 0 syncMethod: 1
syncDirection: 1 syncDirection: 1
syncMode: 0 syncMode: 0
syncInterval: 0 syncInterval: 0
@ -99,7 +99,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d993bac37a92145448c1ea027b3e9ddc, type: 3} m_Script: {fileID: 11500000, guid: d993bac37a92145448c1ea027b3e9ddc, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
syncMethod: 0 syncMethod: 1
syncDirection: 1 syncDirection: 1
syncMode: 0 syncMode: 0
syncInterval: 0 syncInterval: 0
@ -131,7 +131,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7deadf756194d461e9140e42d651693b, type: 3} m_Script: {fileID: 11500000, guid: 7deadf756194d461e9140e42d651693b, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
syncMethod: 0 syncMethod: 1
syncDirection: 0 syncDirection: 0
syncMode: 0 syncMode: 0
syncInterval: 0.1 syncInterval: 0.1