Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Renetcode: disconnect clients that are sending ConnectionRequests #136

Closed
wants to merge 1 commit into from

Conversation

lucaspoffo
Copy link
Owner

If ConnectionRequest is received while a client is connected, this might be a rejoin. If the connection has started more than 10 seconds (could be a late packet) we force a disconnect so the connection workflow can restart.

If ConnectionRequest is received while a client is connected, this might be a rejoin. If the connection has started more than 10 seconds (could be a late packet) we force a disconnect so the connection workflow can restart.
Comment on lines +416 to +425
Packet::Disconnect => {
let client_id = client.client_id;
self.clients[slot] = None;
log::trace!("Client {} requested to disconnect", client_id);
return Ok(ServerResult::ClientDisconnected {
client_id,
addr,
payload: None,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line was removed:
client.state = ConnectionState::Disconnected;

Comment on lines +443 to +445
Packet::ConnectionRequest { .. } => {
// If a ConnectionRequest is received while connected,
// this might be a client trying to rejoin
Copy link
Contributor

@UkoeHB UkoeHB Dec 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this branch can actually run in practice for an honest client, since find_client_mut_by_addr() will fail for the probably-new client address (assuming clients typically generate a new port when reconnecting).

However, this branch can be exploited by a malicious party to force-disconnect clients using packet-sniffed client connection requests.

The function find_client_mut_by_addr() is an important security check in the netcode protocol that stands between connection requests and packet decryption + replay protection. Note that in Packet::decode() the PacketType::ConnectionRequest is not encrypted and doesn't use replay protection, while the other packets have both. Reference: https://gafferongames.com/post/client_server_connection/

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are correct

Hmm, would decoding the private connect token and checking that it is valid and from the same client be enough?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because the attacker is free to use an old connection request from the user.

You could perhaps include a counter inside the connect token (authenticated as part of the private connect token), and auto-disconnect if a higher-generation token shows up for the same client.

Copy link
Contributor

@UkoeHB UkoeHB Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucaspoffo an alternative to a counter would be to use the token's create_timestamp (or expire_timestamp, since only the expiration is currently encoded in the private connect token). If a fresher connect token is received, then the server can safely disconnect the client and reconnect with the new token.

One important detail: the server must emit a disconnect event for a reconnecting client so the server can properly reinitialize them.

This general approach is not strictly as secure as the default netcode protocol. If clients request multiple new connect tokens from the backend, e.g. on a timer while disconnected, then they may reconnect with an older connect token while a newer connect token is only transmitted and cached. If an attacker somehow acquires the newer token then they can use it to force-disconnect the client. However it is better than blindly disconnecting clients, because usually clients will receive connect tokens over a secure channel (and only the renet client -> renet server initial connection is insecure).

@lucaspoffo lucaspoffo closed this Jan 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants