From 9a4893057e8b8d98a0dc4be56a344ec2aca96c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20M=C3=A4der?= Date: Wed, 4 Dec 2024 20:18:53 +0100 Subject: [PATCH] Ratelimit set_presence updates --- changelog.d/18000.bugfix | 1 + .../configuration/config_documentation.md | 16 ++++++++++++++++ synapse/config/ratelimiting.py | 6 ++++++ synapse/rest/client/sync.py | 19 +++++++++++++++++-- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 changelog.d/18000.bugfix diff --git a/changelog.d/18000.bugfix b/changelog.d/18000.bugfix new file mode 100644 index 00000000000..964f051796c --- /dev/null +++ b/changelog.d/18000.bugfix @@ -0,0 +1 @@ +Add ratelimit `rc_set_presence.per_user` to prevent load from excessive presence updates sent by clients . Contributed by @rda0. diff --git a/docs/usage/configuration/config_documentation.md b/docs/usage/configuration/config_documentation.md index 7a48d76bbb1..d0e5a22d9e5 100644 --- a/docs/usage/configuration/config_documentation.md +++ b/docs/usage/configuration/config_documentation.md @@ -1866,6 +1866,22 @@ rc_federation: concurrent: 5 ``` --- +### `rc_set_presence` + +This option sets ratelimiting for presence. + +The `rc_set_presence.per_user` sets ratelimiting how often a specific users' presence +updates are evaluated. Ratelimited presence updates are ignored. +`per_user` defaults to `per_second: 0.1`, `burst_count: 1`. + +Example configuration: +```yaml +rc_set_presence: + per_user: + per_second: 0.1 + burst_count: 1 +``` +--- ### `federation_rr_transactions_per_room_per_second` Sets outgoing federation transaction frequency for sending read-receipts, diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py index 3fa33f5373f..790ec619aae 100644 --- a/synapse/config/ratelimiting.py +++ b/synapse/config/ratelimiting.py @@ -228,3 +228,9 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None: config.get("remote_media_download_burst_count", "500M") ), ) + + self.rc_set_presence_per_user = RatelimitSettings.parse( + config, + "rc_set_presence.per_user", + defaults={"per_second": 0.1, "burst_count": 1}, + ) diff --git a/synapse/rest/client/sync.py b/synapse/rest/client/sync.py index f4ef84a038d..a1dac431c26 100644 --- a/synapse/rest/client/sync.py +++ b/synapse/rest/client/sync.py @@ -24,9 +24,10 @@ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union from synapse.api.constants import AccountDataTypes, EduTypes, Membership, PresenceState -from synapse.api.errors import Codes, StoreError, SynapseError +from synapse.api.errors import Codes, LimitExceededError, StoreError, SynapseError from synapse.api.filtering import FilterCollection from synapse.api.presence import UserPresenceState +from synapse.api.ratelimiting import Ratelimiter from synapse.events.utils import ( SerializeEventConfig, format_event_for_client_v2_without_room_id, @@ -126,6 +127,13 @@ def __init__(self, hs: "HomeServer"): cache_name="sync_valid_filter", ) + # Ratelimiter for set_presence updates, keyed by requester. + self._set_presence_per_user_limiter = Ratelimiter( + store=self.store, + clock=self.clock, + cfg=hs.config.ratelimiting.rc_set_presence_per_user, + ) + async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: # This will always be set by the time Twisted calls us. assert request.args is not None @@ -239,7 +247,14 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: # send any outstanding server notices to the user. await self._server_notices_sender.on_user_syncing(user.to_string()) - affect_presence = set_presence != PresenceState.OFFLINE + # ignore the presence update if the ratelimit is exceeded + try: + await self._set_presence_per_user_limiter.ratelimit(requester) + except LimitExceededError: + affect_presence = False + logger.debug("User set_presence ratelimit exceeded; ignoring it.") + else: + affect_presence = set_presence != PresenceState.OFFLINE context = await self.presence_handler.user_syncing( user.to_string(),