forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			232 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using FishNet.Connection;
 | 
						|
using FishNet.Managing.Logging;
 | 
						|
using FishNet.Object;
 | 
						|
using FishNet.Serializing;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.Runtime.CompilerServices;
 | 
						|
using UnityEngine;
 | 
						|
 | 
						|
namespace FishNet.Managing.Server
 | 
						|
{
 | 
						|
    public sealed partial class ServerManager : MonoBehaviour
 | 
						|
    {
 | 
						|
        #region Private.
 | 
						|
        /// <summary>
 | 
						|
        /// Cached expected level of detail value.
 | 
						|
        /// </summary>
 | 
						|
        private uint _cachedLevelOfDetailInterval;
 | 
						|
        /// <summary>
 | 
						|
        /// Cached value of UseLod.
 | 
						|
        /// </summary>
 | 
						|
        private bool _cachedUseLod;
 | 
						|
        #endregion
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Parses a received network LOD update.
 | 
						|
        /// </summary>
 | 
						|
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
						|
        private void ParseNetworkLODUpdate(PooledReader reader, NetworkConnection conn)
 | 
						|
        {
 | 
						|
            if (!conn.Authenticated)
 | 
						|
                return;
 | 
						|
            if (!NetworkManager.ObserverManager.GetUseNetworkLod())
 | 
						|
            {
 | 
						|
                conn.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection [{conn.ClientId}] sent a level of detail update when the feature is not enabled.");
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            /* If local client then read out entries but do nothing.
 | 
						|
             * Local client doesn't technically have to send LOD because
 | 
						|
             * it's set on the client side but this code is kept in
 | 
						|
             * to simulate actual bandwidth. */
 | 
						|
            if (conn.IsLocalClient)
 | 
						|
            {
 | 
						|
                int w = reader.ReadInt32();
 | 
						|
                for (int i = 0; i < w; i++)
 | 
						|
                    ReadLod(out _, out _);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            uint packetTick = conn.LastPacketTick;
 | 
						|
            //Check if conn can send LOD.
 | 
						|
            uint lastLod = conn.LastLevelOfDetailUpdate;
 | 
						|
            //If previously set see if client is potentially exploiting.
 | 
						|
            if (lastLod != 0)
 | 
						|
            {
 | 
						|
                if ((packetTick - lastLod) < _cachedLevelOfDetailInterval)
 | 
						|
                {
 | 
						|
                    conn.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection [{conn.ClientId}] sent excessive level of detail updates.");
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            //Set last recv lod.
 | 
						|
            conn.LastLevelOfDetailUpdate = packetTick;
 | 
						|
 | 
						|
            //Get server objects to save calls.
 | 
						|
            Dictionary<int, NetworkObject> serverObjects = Objects.Spawned;
 | 
						|
            //Get level of details for this connection and reset them.
 | 
						|
            Dictionary<NetworkObject, byte> levelOfDetails = conn.LevelOfDetails;
 | 
						|
 | 
						|
            int written = reader.ReadInt32();
 | 
						|
 | 
						|
            /* //TODO There is still an instance where client could simply say no LODs need
 | 
						|
             * updating and never update for their objects in the first place. This can be resolved
 | 
						|
             * by adding an observed object count to each connection and compare that to
 | 
						|
             * the size of the LOD collection. */
 | 
						|
 | 
						|
            //Only process if some are written.
 | 
						|
            if (written > 0)
 | 
						|
            {
 | 
						|
                //Maximum infractions before a kick.
 | 
						|
                const int maximumInfractions = 15;
 | 
						|
                int currentInfractions = conn.LevelOfDetailInfractions;
 | 
						|
                int infractionsCounted = 0;
 | 
						|
 | 
						|
                /* If the connection has no objects then LOD isn't capable
 | 
						|
                 * of being calculated. It's possible the players object was destroyed after
 | 
						|
                 * the LOD sent but we don't know for sure without adding extra checks.
 | 
						|
                 * Rather than add recently destroyed player object checks if there are
 | 
						|
                 * no player objects then just add an infraction. The odds of this happening regularly
 | 
						|
                 * are pretty slim. */
 | 
						|
                if (conn.Objects.Count == 0)
 | 
						|
                {
 | 
						|
                    if (AddInfraction(3))
 | 
						|
                    {
 | 
						|
                        conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] has sent an excessive number of level of detail updates without having any player objects spawned.");
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                /* If written is more than spawned + recently despawned then
 | 
						|
                 * the client is likely trying to exploit. */
 | 
						|
                if (written > (Objects.Spawned.Count + Objects.RecentlyDespawnedIds.Count))
 | 
						|
                {
 | 
						|
                    conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] sent a level of detail update for {written} items which exceeds spawned and recently despawned count.");
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                Vector3 connObjectPosition = Vector3.zero;
 | 
						|
                //Pick a random object from the player to sample.
 | 
						|
                int objectIndex = UnityEngine.Random.Range(0, conn.Objects.Count);
 | 
						|
                int connObjectIteration = 0;
 | 
						|
                foreach (NetworkObject n in conn.Objects)
 | 
						|
                {
 | 
						|
                    if (connObjectIteration == objectIndex)
 | 
						|
                    {
 | 
						|
                        connObjectPosition = n.transform.position;
 | 
						|
                        //Flag to indicate found.
 | 
						|
                        objectIndex = -1;
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                //Server somehow messed up. Should not be possible.
 | 
						|
                if (objectIndex != -1)
 | 
						|
                {
 | 
						|
                    NetworkManager.LogError($"An object index of {objectIndex} could not be populated. Connection [{conn.ClientId}] object count is {conn.Objects.Count}.");
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                //Sample at most x entries per update.
 | 
						|
                int samplesRemaining = 10;
 | 
						|
                //Chance to sample an update.
 | 
						|
                const float sampleChance = 0.05f;
 | 
						|
 | 
						|
                List<float> lodDistances = NetworkManager.ObserverManager.GetLevelOfDetailDistances();
 | 
						|
                int lodDistancesCount = lodDistances.Count;
 | 
						|
                for (int i = 0; i < written; i++)
 | 
						|
                {
 | 
						|
                    int objectId;
 | 
						|
                    byte lod;
 | 
						|
                    ReadLod(out objectId, out lod);
 | 
						|
 | 
						|
                    //Lod is not possible.
 | 
						|
                    if (lod >= lodDistancesCount)
 | 
						|
                    {
 | 
						|
                        conn.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection [{conn.ClientId}] provided a level of detail index which is out of bounds.");
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
 | 
						|
                    //Found in spawned, update lod.
 | 
						|
                    if (serverObjects.TryGetValue(objectId, out NetworkObject nob))
 | 
						|
                    {
 | 
						|
                        //Value is unchanged.
 | 
						|
                        if (levelOfDetails.TryGetValue(nob, out byte oldLod))
 | 
						|
                        {
 | 
						|
                            bool oldMatches = (oldLod == lod);
 | 
						|
                            if (oldMatches && AddInfraction())
 | 
						|
                            {
 | 
						|
                                conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] has excessively sent unchanged LOD information.");
 | 
						|
                                return;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        //If to sample.
 | 
						|
                        if (samplesRemaining > 0 && UnityEngine.Random.Range(0f, 1f) <= sampleChance)
 | 
						|
                        {
 | 
						|
                            samplesRemaining--;
 | 
						|
                            /* Only check if lod is less than maximum.
 | 
						|
                             * If the client is hacking lods to specify maximum
 | 
						|
                             * they are only doing the server a favor and hurting
 | 
						|
                             * themselves with slower updates. */
 | 
						|
                            if (lod < (lodDistancesCount - 1))
 | 
						|
                            {
 | 
						|
                                float specifiedLodDistance = lodDistances[lod];
 | 
						|
                                float sqrMag = Vector3.SqrMagnitude(connObjectPosition - nob.transform.position);
 | 
						|
                                /* If the found distance is actually larger than what client specified
 | 
						|
                                 * then it's possible client may be sending fake LODs. */
 | 
						|
                                if (sqrMag > specifiedLodDistance)
 | 
						|
                                {
 | 
						|
                                    if (AddInfraction())
 | 
						|
                                    {
 | 
						|
                                        conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] provided an excessive number of incorrect LOD values.");
 | 
						|
                                        return;
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
 | 
						|
                        levelOfDetails[nob] = lod;
 | 
						|
                    }
 | 
						|
                    //Not found in spawn; validate that client isn't trying to exploit.
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        //Too many infractions.
 | 
						|
                        if (AddInfraction())
 | 
						|
                        {
 | 
						|
                            conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] has accumulated excessive level of detail infractions.");
 | 
						|
                            return;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 
 | 
						|
                //Adds an infraction returning if maximum infractions have been exceeded.
 | 
						|
                bool AddInfraction(int count = 1)
 | 
						|
                {
 | 
						|
                    /* Only increase infractions at most 3 per iteration.
 | 
						|
                    * This is to prevent a kick if the client perhaps had
 | 
						|
                    * a massive lag spike. */
 | 
						|
                    if (infractionsCounted < 3)
 | 
						|
                        infractionsCounted += count;
 | 
						|
 | 
						|
                    bool overLimit = ((currentInfractions + infractionsCounted) >= maximumInfractions);
 | 
						|
                    return overLimit;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            //Reads a LOD.
 | 
						|
            void ReadLod(out int lObjectId, out byte lLod)
 | 
						|
            {
 | 
						|
                lObjectId = reader.ReadNetworkObjectId();
 | 
						|
                lLod = reader.ReadByte();
 | 
						|
            }
 | 
						|
 | 
						|
            //Remove an infraction. This will steadily remove infractions over time.
 | 
						|
            if (conn.LevelOfDetailInfractions > 0)
 | 
						|
                conn.LevelOfDetailInfractions--;
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
}
 |