I’m trying to control the movement of a 3D fps player without relying on the client, what I mean with this is that even if NGO is server-authoritative, when running a server RPC, the code is being run in the server but the code itself is being provided by the client, couldn’t someone change this function and for example increase the speed of the player? is there any way for the client to send the server the action so that the server would use the unmodified function that’s stored in the server?
For example, the client tells the server to jump and the server impulses the player up.
The client “owns” the input so it cannot be validated. The Netcode doc mention this at one point.
Normally what games do is to allow the client to perform any input but also send the input action to the server who then validates it. If for example the client modified its jump force to be higher and the server runs the authoritative version and updates the player’s character on the server side, this will be sent back over to the client if a NetworkTransform is used and alters the client player’s position.
However, there are downsides to this, most importantly jitter because the server is always a couple ms late. I’m not sure if and how Netcode compensates for this. I think it already does a good job but can’t say for sure because besides initial tests I was using ClientNetworkTransform because I was aiming for smoother gameplay and trusting clients.
Yes, my problem is that the server is not capable of not running a function sent as a server RPC, this is an issue as the client could modify the function in order to change game variables or run malicious code on the server.
Other case I’m thinking of is for example for inventory managing, you could make a function to drop an item in your inventory, it would take care of instantiating the dropped object so it can be picked up and of deleting the item from where its stored. Any client could delete the section deleting the items and basically duplicate them.
What I’m trying to get to is how to call functions on the client that are only defined on the server. making it imposible to modify the code in the function.
The RPC triggers the server-side code to run with the parameters provided, so only the parameters need to be passed to the server. As you say having the client be able pass code for the server to run would lead to all sorts of security problems.
For instance, if a client did that and is now in posession of an invincibility item and uses that, it will send an RPC to the server. The server then checks before running the necessary simulation code whether the client actually has that item. It determines the client does not have that item, and instead instructs the client to be turned into a defenseless chicken.
(or kicks & bans the player as a less fun alternative)
That’s the things the server needs to validate to ensure security but of course he cannot possibly infer everything, specifically input. Who is to say that the client didn’t press left/right alternating every frame? It CAN happen but an excess amount would indicate cheating - but at which point the server determines this to be cheating is up for discussion.
Or consider jump force: the client should NOT send its jump force to the server, the server uses the jump force that it knows is the correct force. If the client were to cheat that: see above.
But verifying movement and aiming is the most difficult to do on server side, if not impossible. Most games allow for clients to cheat in that regard but without consequence because the local client might be moving faster but for the server and all other clients the player is still here and not way over there, so hits from other players are still registered while the cheating client has literally no idea who is possibly able to hit him, or who he can hit himself, making that kind of cheat pointless.
Also, if the client position (possibly extrapolated with ping/RTT) is too far off from what the server simulates, the server should then send a “reset client position and velocity” RPC to the client to force him back to be within the bounds of the simulation.
So, I finally found a solution to my problem, My solution was to create a namespace with a class inheriting from NetworkBehabiour and define all server RPCs there as virtual voids(just defined no code in them) the client-side function will inherit from this new class and in this way, the client can call the serverRPC. The server-side script will also inherit from the new class and in this way you can override the RPC and write the code only for the server-side.
Before, if the client and server functions had to be identic for it to be run, but with this, when the client calls the function, it will be calling an empty function. It seems like the server is comparing the empty functions and don’t take into account the override making it possible to run code only available on server side.
This is a good find, thanks for sharing!
You think it would work with an interface and default interface implementation too? I might give that a try, though it requires at least Unity 2021.2 I think.
Does the […Rpc] attribute go above the virtual empty method? I wonder what happens if the server override method also adds the attribute, possibly with conflicting parameters.
Assume we have a ServerRpc method in a class. The client is the only one who can call that method. However, logically the client does NOT run this method! For the client, “calling” this method is in fact more like telling Netcode to send a message to the server, instructing the server to execute that method.
Now the code contained within that method would normally be compiled into the client executable as well - or is it? Maybe Netcode is clever enough behind the scenes to not do that.
Still, even if the code were in there, the client never actually executes that code. At worst, a client could more easily look at the algorithm the server executes to get a better undertstanding of how the computation works. But that shouldn’t be an issue. Nor should patching the exe to run the server-side code on the client because the server is going to overwrite any changes in that anyway after the ServerRpc ran.
The only thing the client does is send parameters to the server. The server can then perform at least bounds checks on these parameters, perhaps even sanity checks too. But that’s about all the server can do, besides running the simulation i a server authoritative way as much as possible.
When you make a virtual ServerRpc method and override this in a server-only class and even if you made sure that this server-only class is only compiled into the (dedicated) server executable, you have gained no additional security. At best some obscurity on the part of analyzing the server’s code. The client’s behaviour doesn’t change, he is still just sending out a network message with some parameters to the server, and those parameters could still be modified by the client.
Please correct me if I’m not looking at the issue from the right angle somehow.
yes, you need to add the attribute both times, when you define it and when you override it, the only difference is changing “virtual” for “override” and adding the code on the override function. Also regarding your other comment, I wanted the code to be only server-sided for anticheat reasons, if hackers cant see the code it is more difficult for them to find vulnerabilities, also doing this will make the code more light as the server logic and the client logic don’t need to be in the same script.
Also I had been going through the documentation and up to my understanding, the client calls the RPC function and the execution of the client-defined rpc woud be canceled and instead sends the function to the server with the parameters, then that is added to a queue on the server side. When its time to run that function the server will look for that function on the server-side and run it with the parameters provided by the client.
Talking about this, I had been implementing this for the movement logic and what i hade been doing is:
For example for jumping:
_Client Inputs the jump key
_The client sided code checks If it is grounded(avoiding normal non-hacked clients to send unnesesary packets)
_Server receives this RPC and overrides it
_The server then checks again if the player is grounded
_If it is, it will add an up impulse using the variable locally stored in the server.
For movement, a similar approach is taken, the only difference is that the client sends the direction it wants to move as a parameter(the groundcheck is also done in both), the server then moves the player in that direction at a speed depending on the player state(walking/running/crouching/stunned/etc). Also note that the movement direction is sent as a unreliable packet while the state function is sent as a reliable packet for a better performance and avoid an overflow in laggy situations.