Skip to content

Commit f0b8a92

Browse files
EmandMxmanningNoelStephensUnitynetcode-ci-service
authored
feat: Server enforced rpc invoke permission (#3731)
* Secure RpcMessage against SenderClientId spoofing When a server receives an RpcMessage, it should update the SenderClientId to match the SenderId provided by the messaging manager. This is to prevent modified clients from spoofing their SenderClientId as other clients. * Replaced RequireOwnership with a new RpcInvokePermission that is respected from the server for both direct and proxy RPCs. * Replaced RequireOwnership with a new RpcInvokePermission that is respected from the server for both direct and proxy RPCs. * Add missing switch case * Another missing switch case * Restore and deprecate RequireOwnership in favor of RpcInvokePermission * Only do permission validations on server context.SenderId will be the server for a client receiving the proxied message, so the checks will not be accurate. The checks are also unnecessary considering the checks have already been done by the server. * ClientRpc properly assigned InvokePermission Assigning ClientRpc.InvokePermission in the constructor was incorrect. Since we can check if it is a ClientRpc easily, I just do that in ILPP * Move test files * Get logic working and add tests * Mark ServerRpc.RequireOwnership as deprecated * small fixes * Add back RpcAttributeParams.RequireOwnership * Update CHANGELOG * Add InvokePermission attribute when RequireOwnership is defined * Fix whitespace * fix __registerRpc breaking change * second try at fixing breaking change * Fix ilpp to log message if NetworkManager is not connected or listening * Update com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md Co-authored-by: Noel Stephens <[email protected]> * Update com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md Co-authored-by: Noel Stephens <[email protected]> * Update com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md Co-authored-by: Noel Stephens <[email protected]> * Update log messages * Fix naming in networkvariable.md doc * Update com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md Co-authored-by: Noel Stephens <[email protected]> * Update com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md Co-authored-by: Noel Stephens <[email protected]> * Add invocation order documentation --------- Co-authored-by: xmanning <[email protected]> Co-authored-by: xmanning <[email protected]> Co-authored-by: Noel Stephens <[email protected]> Co-authored-by: Unity Netcode CI <[email protected]>
1 parent b7dcc09 commit f0b8a92

39 files changed

+995
-132
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13+
- `RpcInvokePermission` to control who has permission to invoke specific RPC methods. (#3731)
1314
- Added NetworkRigidbody documentation section. (#3664)
1415
- Added new fields to the `SceneMap` struct when using Unity 6.3 or higher. These fields allow referencing scene handles via the new `SceneHandle` struct. (#3734)
1516

@@ -25,11 +26,11 @@ Additional documentation and release notes are available at [Multiplayer Documen
2526

2627
### Deprecated
2728

29+
- Deprecated all `RequireOwnership` fields around the RPCs in favor of the `RpcInvokePermission`. (#3731)
2830
- On Unity 6.5 some `SceneMap` fields that use an `int` to represent a `SceneHandle` are deprecated. (#3734)
2931

3032
### Removed
3133

32-
3334
### Fixed
3435

3536
- Multiple disconnect events from the same transport will no longer disconnect the host. (#3707)

com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# RPC
22

3-
Any process can communicate with any other process by sending a remote procedure call (RPC). As of Netcode for GameObjects version 1.8.0, the `Rpc` attribute encompasses server to client RPCs, client to server RPCs, and client to client RPCs.
3+
Any process can communicate with any other process by sending a remote procedure call (RPC). The `Rpc` attribute is the recommended attribute to use when declaring RPC methods. The `Rpc` attribute's parameters define the receivers and execution rights for the RPC method.
44

55
![](../../images/sequence_diagrams/RPCs/ServerRPCs.png)
66

@@ -204,7 +204,7 @@ There are a few other parameters that can be passed to either the `Rpc` attribut
204204
| Parameter | Description |
205205
| ----------------------- | ------------------------------------------------------------ |
206206
| `Delivery` | Controls whether the delivery is reliable (default) or unreliable.<br /><br />Options: `RpcDelivery.Reliable` or `RpcDelivery.Unreliable`<br />Default: `RpcDelivery.Reliable` |
207-
| `RequireOwnership` | If `true`, this RPC throws an exception if invoked by a player that does not own the object. This is in effect for server-to-client, client-to-server, and client-to-client RPCs - i.e., a server-to-client RPC will still fail if the server is not the object's owner.<br /><br />Default: `false` |
207+
| `InvokePermission` | Sets an RPC's invocation permissions.<br /><br />Options:<br /> `RpcInvokePermission.Server` - This RPC throws an exception if invoked by a game client that is not the server.<br />`RpcInvokePermission.Owner` - This RPC throws an exception if invoked by a game client that does not own the object.<br />`RpcInvokePermission.Everyone` - This can be invoked by any connected game client.<br />Default: `RpcInvokePermission.Everyone` |
208208
| `DeferLocal` | If `true`, RPCs that execute locally will be deferred until the start of the next frame, as if they had been sent over the network. (They will not actually be sent over the network, but will be treated as if they were.) This is useful for mutually recursive RPCs on hosts, where sending back and forth between the server and the "host client" will cause a stack overflow if each RPC is executed instantly; simulating the flow of RPCs between remote client and server enables this flow to work the same in both contexts.<br /><br />Default: `false` |
209209
| `AllowTargetOverride` | By default, any `SendTo` value other than `SendTo.SpecifiedInParams` is a hard-coded value that cannot be changed. Setting this to `true` allows you to provide an alternate target at runtime, while using the `SendTo` value as a fallback if no runtime value is provided. |
210210

@@ -215,6 +215,116 @@ There are a few other parameters that can be passed to either the `Rpc` attribut
215215
| `Target` | Runtime override destination for the RPC. (See above for more details.) Populating this value will throw an exception unless either the `SendTo` value for the RPC is `SendTo.SpecifiedInParams`, or `AllowTargetOverride` is `true`.<br /><br />Default: `null` |
216216
| `LocalDeferMode` | Overrides the `DeferLocal` value. `DeferLocalMode.Defer` causes this particular invocation of this RPC to be deferred until the next frame even if `DeferLocal` is `false`, while `DeferLocalMode.SendImmediate` causes the RPC to be executed immediately on the local machine even if `DeferLocal` is `true`. `DeferLocalMode.Default` does whatever the `DeferLocal` value in the attribute is configured to do.<br /><br />Options: `DeferLocalMode.Default`, `DeferLocalMode.Defer`, `DeferLocalMode.SendImmediate`<br />Default: `DeferLocalMode.Default` |
217217

218+
## Invocation order
219+
220+
Rpc message sent with `RpcDelivery.Reliable` will be sent and invoked on other game clients in the same order as they were called on the local game client.
221+
222+
```csharp
223+
[Rpc(SendTo.Server)]
224+
public void OpenDoorRpc(int doorId, RpcParams rpcParams)
225+
{
226+
Debug.Log($"client {rpcParams.Receive.SenderClientId} has opened door {doorId}");
227+
228+
// Server can handle door opening here
229+
}
230+
231+
[Rpc(SendTo.Server)]
232+
void OpenChestRPC(int chestId, RpcParams rpcParams)
233+
{
234+
Debug.Log($"client {rpcParams.Receive.SenderClientId} has opened chest {chestId}");
235+
236+
// Server can handle door opening here
237+
}
238+
239+
void Update()
240+
{
241+
if (IsClient && Input.GetKeyDown(KeyCode.O))
242+
{
243+
OpenDoorRpc(1)
244+
OpenDoorRpc(2)
245+
OpenChestRpc(5)
246+
}
247+
248+
// Other clients will log:
249+
//
250+
// "client 1 has opened door 1"
251+
// "client 1 has opened door 2"
252+
// "client 1 has opened chest 5"
253+
}
254+
```
255+
256+
> [!Warning]
257+
> Invocation order is not guaranteed with nested RPC invocations that include targets that may invoke locally. Invocation order is also not guaranteed when using `RpcDelivery.Unreliable`
258+
259+
### Deferring local invocation
260+
261+
Invoking an RPC from within another RPC introduces the risk that the local RPC may invoke before messages are sent to other game clients. This will result in the RPC message for the inner RPC invocation being sent before the message for the outer RPC.
262+
263+
```csharp
264+
[Rpc(SendTo.Everyone)]
265+
public void TryOpenDoorRpc(int doorId, RpcParams rpcParams)
266+
{
267+
Debug.Log($"client {rpcParams.Receive.SenderClientId} is trying to open door {doorId}");
268+
269+
if (HasAuthority) {
270+
// Authority handles opening the door here
271+
272+
// If the authority is invoking TryOpenDoorRpc locally before the authority has sent TryOpenDoorRpc to other clients, OpenDoorRpc will be sent before TryOpenDoorRpc.
273+
OpenDoorRpc(doorId);
274+
}
275+
}
276+
277+
[Rpc(SendTo.Everyone)]
278+
public void OpenDoorRpc(int doorId, RpcParams rpcParams)
279+
{
280+
Debug.Log($"client {rpcParams.Receive.SenderClientId} marked door {doorId} as open");
281+
}
282+
283+
void Update()
284+
{
285+
if (Input.GetKeyDown(KeyCode.O))
286+
{
287+
// Invocation of TryOpenDoorRpc and OpenDoorRpc may be inverted depending on the context in which TryOpenDoorRpc is invoked
288+
TryOpenDoorRpc(20);
289+
}
290+
}
291+
```
292+
293+
Use the RPC `LocalDeferMode` to resolve issue. Configuring the RPC to be deferred when invoked locally will ensure that any outer RPC messages are always sent before the inner function is invoked.
294+
295+
```csharp
296+
// An RPC can be configured to defer the local invocation in the attribute definition
297+
[Rpc(SendTo.Everyone, DeferLocal = true)]
298+
public void TryOpenDoorRpc(int doorId, RpcParams rpcParams = default)
299+
{
300+
Debug.Log($"client {rpcParams.Receive.SenderClientId} is trying to open door {doorId}");
301+
302+
if (HasAuthority) {
303+
304+
// Authority handles opening the door here
305+
306+
// Defer mode can also be passed in at the call site.
307+
OpenDoorRpc(doorId, LocalDeferMode.Defer);
308+
}
309+
}
310+
311+
[Rpc(SendTo.Everyone)]
312+
public void OpenDoorRpc(int doorId, RpcParams rpcParams = default)
313+
{
314+
Debug.Log($"client {rpcParams.Receive.SenderClientId} marked door {doorId} as open");
315+
}
316+
317+
void Update()
318+
{
319+
if (Input.GetKeyDown(KeyCode.O))
320+
{
321+
// TryOpenDoorRpc is defined with DeferLocal
322+
// DeferLocal ensures that RPC messages are sent to all other targets before the RPC is invoked locally
323+
TryOpenDoorRpc(20);
324+
}
325+
}
326+
```
327+
218328
## Additional resources
219329

220330
* [RPC parameters](rpc-params.md)

com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ This works the same way with dynamically spawned NetworkObjects.
145145

146146
The [synchronization and notification example](#synchronization-and-notification-example) highlights the differences between synchronizing a `NetworkVariable` with newly-joining clients and notifying connected clients when a `NetworkVariable` changes, but it doesn't provide any concrete example usage.
147147

148-
The `OnValueChanged` example shows a simple server-authoritative `NetworkVariable` being used to track the state of a door (that is, open or closed) using a non-ownership-based server RPC. With `RequireOwnership = false` any client can notify the server that it's performing an action on the door. Each time the door is used by a client, the `Door.ToggleServerRpc` is invoked and the server-side toggles the state of the door. When the `Door.State.Value` changes, all connected clients are synchronized to the (new) current `Value` and the `OnStateChanged` method is invoked locally on each client.
148+
The `OnValueChanged` example shows a simple server-authoritative `NetworkVariable` being used to track the state of a door (that is, open or closed) using an RPC that is sent to the server. Each time the door is used by a client, the `Door.ToggleStateRpc` is invoked and the server-side toggles the state of the door. When the `Door.State.Value` changes, all connected clients are synchronized to the (new) current `Value` and the `OnStateChanged` method is invoked locally on each client.
149149

150150
```csharp
151151
public class Door : NetworkBehaviour
@@ -180,7 +180,7 @@ public class Door : NetworkBehaviour
180180
}
181181

182182
[Rpc(SendTo.Server)]
183-
public void ToggleServerRpc()
183+
public void ToggleStateRpc()
184184
{
185185
// this will cause a replication over the network
186186
// and ultimately invoke `OnValueChanged` on receivers

com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,4 @@ When requesting ownership of a NetworkObject using [`NetworkObject.RequestOwners
5858

5959
* [Authority](authority.md)
6060
* [Client-server](client-server.md)
61-
* [Distributed authority](distributed-authority.md)
61+
* [Distributed authority](distributed-authority.md)

0 commit comments

Comments
 (0)