Skip to content

Commit

Permalink
Ratelimit set_presence updates
Browse files Browse the repository at this point in the history
  • Loading branch information
rda0 committed Dec 5, 2024
1 parent 0db5d24 commit 9a48930
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog.d/18000.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ratelimit `rc_set_presence.per_user` to prevent load from excessive presence updates sent by clients . Contributed by @rda0.
16 changes: 16 additions & 0 deletions docs/usage/configuration/config_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions synapse/config/ratelimiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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},
)
19 changes: 17 additions & 2 deletions synapse/rest/client/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit 9a48930

Please sign in to comment.