Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Make all the rate limiting options more consistent #5181

Merged
merged 7 commits into from
May 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5181.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ratelimiting configuration for clients sending messages and the federation server has been altered to match login ratelimiting. The old configuration names will continue working. Check the sample config for details of the new names.
53 changes: 25 additions & 28 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -446,21 +446,15 @@ log_config: "CONFDIR/SERVERNAME.log.config"

## Ratelimiting ##

# Number of messages a client can send per second
#
#rc_messages_per_second: 0.2

# Number of message a client can send before being throttled
#
#rc_message_burst_count: 10.0

# Ratelimiting settings for registration and login.
# Ratelimiting settings for client actions (registration, login, messaging).
#
# Each ratelimiting configuration is made of two parameters:
# - per_second: number of requests a client can send per second.
# - burst_count: number of requests a client can send before being throttled.
#
# Synapse currently uses the following configurations:
# - one for messages that ratelimits sending based on the account the client
# is using
# - one for registration that ratelimits registration requests based on the
# client's IP address.
# - one for login that ratelimits login requests based on the client's IP
Expand All @@ -473,6 +467,10 @@ log_config: "CONFDIR/SERVERNAME.log.config"
#
# The defaults are as shown below.
#
#rc_message:
# per_second: 0.2
# burst_count: 10
#
#rc_registration:
# per_second: 0.17
# burst_count: 3
Expand All @@ -488,29 +486,28 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# per_second: 0.17
# burst_count: 3

# The federation window size in milliseconds
#
#federation_rc_window_size: 1000

# The number of federation requests from a single server in a window
# before the server will delay processing the request.
# Ratelimiting settings for incoming federation
#
#federation_rc_sleep_limit: 10

# The duration in milliseconds to delay processing events from
# remote servers by if they go over the sleep limit.
#
#federation_rc_sleep_delay: 500

# The maximum number of concurrent federation requests allowed
# from a single server
# The rc_federation configuration is made up of the following settings:
# - window_size: window size in milliseconds
# - sleep_limit: number of federation requests from a single server in
# a window before the server will delay processing the request.
# - sleep_delay: duration in milliseconds to delay processing events
# from remote servers by if they go over the sleep limit.
# - reject_limit: maximum number of concurrent federation requests
# allowed from a single server
# - concurrent: number of federation requests to concurrently process
# from a single server
#
#federation_rc_reject_limit: 50

# The number of federation requests to concurrently process from a
# single server
# The defaults are as shown below.
#
#federation_rc_concurrent: 3
#rc_federation:
# window_size: 1000
# sleep_limit: 10
# sleep_delay: 500
# reject_limit: 50
# concurrent: 3

# Target outgoing federation transaction frequency for sending read-receipts,
# per-room.
Expand Down
115 changes: 73 additions & 42 deletions synapse/config/ratelimiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,83 @@


class RateLimitConfig(object):
def __init__(self, config):
self.per_second = config.get("per_second", 0.17)
self.burst_count = config.get("burst_count", 3.0)
def __init__(self, config, defaults={"per_second": 0.17, "burst_count": 3.0}):
self.per_second = config.get("per_second", defaults["per_second"])
self.burst_count = config.get("burst_count", defaults["burst_count"])


class RatelimitConfig(Config):
class FederationRateLimitConfig(object):
_items_and_default = {
"window_size": 10000,
"sleep_limit": 10,
"sleep_delay": 500,
"reject_limit": 50,
"concurrent": 3,
}

def __init__(self, **kwargs):
for i in self._items_and_default.keys():
setattr(self, i, kwargs.get(i) or self._items_and_default[i])


class RatelimitConfig(Config):
def read_config(self, config):
self.rc_messages_per_second = config.get("rc_messages_per_second", 0.2)
self.rc_message_burst_count = config.get("rc_message_burst_count", 10.0)

# Load the new-style messages config if it exists. Otherwise fall back
# to the old method.
if "rc_message" in config:
self.rc_message = RateLimitConfig(
config["rc_message"], defaults={"per_second": 0.2, "burst_count": 10.0}
)
else:
self.rc_message = RateLimitConfig(
{
"per_second": config.get("rc_messages_per_second", 0.2),
"burst_count": config.get("rc_message_burst_count", 10.0),
}
)

# Load the new-style federation config, if it exists. Otherwise, fall
# back to the old method.
if "federation_rc" in config:
self.rc_federation = FederationRateLimitConfig(**config["rc_federation"])
else:
self.rc_federation = FederationRateLimitConfig(
**{
"window_size": config.get("federation_rc_window_size"),
"sleep_limit": config.get("federation_rc_sleep_limit"),
"sleep_delay": config.get("federation_rc_sleep_delay"),
"reject_limit": config.get("federation_rc_reject_limit"),
"concurrent": config.get("federation_rc_concurrent"),
}
)

self.rc_registration = RateLimitConfig(config.get("rc_registration", {}))

rc_login_config = config.get("rc_login", {})
self.rc_login_address = RateLimitConfig(rc_login_config.get("address", {}))
self.rc_login_account = RateLimitConfig(rc_login_config.get("account", {}))
self.rc_login_failed_attempts = RateLimitConfig(
rc_login_config.get("failed_attempts", {}),
rc_login_config.get("failed_attempts", {})
)

self.federation_rc_window_size = config.get("federation_rc_window_size", 1000)
self.federation_rc_sleep_limit = config.get("federation_rc_sleep_limit", 10)
self.federation_rc_sleep_delay = config.get("federation_rc_sleep_delay", 500)
self.federation_rc_reject_limit = config.get("federation_rc_reject_limit", 50)
self.federation_rc_concurrent = config.get("federation_rc_concurrent", 3)

self.federation_rr_transactions_per_room_per_second = config.get(
"federation_rr_transactions_per_room_per_second", 50,
"federation_rr_transactions_per_room_per_second", 50
)

def default_config(self, **kwargs):
return """\
## Ratelimiting ##

# Number of messages a client can send per second
#
#rc_messages_per_second: 0.2

# Number of message a client can send before being throttled
#
#rc_message_burst_count: 10.0

# Ratelimiting settings for registration and login.
# Ratelimiting settings for client actions (registration, login, messaging).
#
# Each ratelimiting configuration is made of two parameters:
# - per_second: number of requests a client can send per second.
# - burst_count: number of requests a client can send before being throttled.
#
# Synapse currently uses the following configurations:
# - one for messages that ratelimits sending based on the account the client
# is using
# - one for registration that ratelimits registration requests based on the
# client's IP address.
# - one for login that ratelimits login requests based on the client's IP
Expand All @@ -77,6 +105,10 @@ def default_config(self, **kwargs):
#
# The defaults are as shown below.
#
#rc_message:
# per_second: 0.2
# burst_count: 10
#
#rc_registration:
# per_second: 0.17
# burst_count: 3
Expand All @@ -92,29 +124,28 @@ def default_config(self, **kwargs):
# per_second: 0.17
# burst_count: 3

# The federation window size in milliseconds
#
#federation_rc_window_size: 1000

# The number of federation requests from a single server in a window
# before the server will delay processing the request.
#
#federation_rc_sleep_limit: 10

# The duration in milliseconds to delay processing events from
# remote servers by if they go over the sleep limit.
# Ratelimiting settings for incoming federation
#
#federation_rc_sleep_delay: 500

# The maximum number of concurrent federation requests allowed
# from a single server
# The rc_federation configuration is made up of the following settings:
# - window_size: window size in milliseconds
# - sleep_limit: number of federation requests from a single server in
# a window before the server will delay processing the request.
# - sleep_delay: duration in milliseconds to delay processing events
# from remote servers by if they go over the sleep limit.
# - reject_limit: maximum number of concurrent federation requests
# allowed from a single server
# - concurrent: number of federation requests to concurrently process
# from a single server
#
#federation_rc_reject_limit: 50

# The number of federation requests to concurrently process from a
# single server
# The defaults are as shown below.
#
#federation_rc_concurrent: 3
#rc_federation:
# window_size: 1000
# sleep_limit: 10
# sleep_delay: 500
# reject_limit: 50
# concurrent: 3

# Target outgoing federation transaction frequency for sending read-receipts,
# per-room.
Expand Down
6 changes: 1 addition & 5 deletions synapse/federation/transport/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,7 @@ def __init__(self, hs, servlet_groups=None):
self.authenticator = Authenticator(hs)
self.ratelimiter = FederationRateLimiter(
self.clock,
window_size=hs.config.federation_rc_window_size,
sleep_limit=hs.config.federation_rc_sleep_limit,
sleep_msec=hs.config.federation_rc_sleep_delay,
reject_limit=hs.config.federation_rc_reject_limit,
concurrent_requests=hs.config.federation_rc_concurrent,
config=hs.config.rc_federation,
)

self.register_servlets()
Expand Down
4 changes: 2 additions & 2 deletions synapse/handlers/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ def ratelimit(self, requester, update=True):
messages_per_second = override.messages_per_second
burst_count = override.burst_count
else:
messages_per_second = self.hs.config.rc_messages_per_second
burst_count = self.hs.config.rc_message_burst_count
messages_per_second = self.hs.config.rc_message.per_second
burst_count = self.hs.config.rc_message.burst_count

allowed, time_allowed = self.ratelimiter.can_do_action(
user_id, time_now,
Expand Down
23 changes: 13 additions & 10 deletions synapse/rest/client/v2_alpha/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
SynapseError,
UnrecognizedRequestError,
)
from synapse.config.ratelimiting import FederationRateLimitConfig
from synapse.config.server import is_threepid_reserved
from synapse.http.servlet import (
RestServlet,
Expand Down Expand Up @@ -153,16 +154,18 @@ def __init__(self, hs):
self.registration_handler = hs.get_registration_handler()
self.ratelimiter = FederationRateLimiter(
hs.get_clock(),
# Time window of 2s
window_size=2000,
# Artificially delay requests if rate > sleep_limit/window_size
sleep_limit=1,
# Amount of artificial delay to apply
sleep_msec=1000,
# Error with 429 if more than reject_limit requests are queued
reject_limit=1,
# Allow 1 request at a time
concurrent_requests=1,
FederationRateLimitConfig(
# Time window of 2s
window_size=2000,
# Artificially delay requests if rate > sleep_limit/window_size
sleep_limit=1,
# Amount of artificial delay to apply
sleep_msec=1000,
# Error with 429 if more than reject_limit requests are queued
reject_limit=1,
# Allow 1 request at a time
concurrent_requests=1,
)
)

@defer.inlineCallbacks
Expand Down
47 changes: 15 additions & 32 deletions synapse/util/ratelimitutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,31 +30,14 @@


class FederationRateLimiter(object):
def __init__(self, clock, window_size, sleep_limit, sleep_msec,
reject_limit, concurrent_requests):
def __init__(self, clock, config):
"""
Args:
clock (Clock)
window_size (int): The window size in milliseconds.
sleep_limit (int): The number of requests received in the last
`window_size` milliseconds before we artificially start
delaying processing of requests.
sleep_msec (int): The number of milliseconds to delay processing
of incoming requests by.
reject_limit (int): The maximum number of requests that are can be
queued for processing before we start rejecting requests with
a 429 Too Many Requests response.
concurrent_requests (int): The number of concurrent requests to
process.
config (FederationRateLimitConfig)
"""
self.clock = clock

self.window_size = window_size
self.sleep_limit = sleep_limit
self.sleep_msec = sleep_msec
self.reject_limit = reject_limit
self.concurrent_requests = concurrent_requests

self._config = config
self.ratelimiters = {}

def ratelimit(self, host):
Expand All @@ -76,25 +59,25 @@ def ratelimit(self, host):
host,
_PerHostRatelimiter(
clock=self.clock,
window_size=self.window_size,
sleep_limit=self.sleep_limit,
sleep_msec=self.sleep_msec,
reject_limit=self.reject_limit,
concurrent_requests=self.concurrent_requests,
config=self._config,
)
).ratelimit()


class _PerHostRatelimiter(object):
def __init__(self, clock, window_size, sleep_limit, sleep_msec,
reject_limit, concurrent_requests):
def __init__(self, clock, config):
"""
Args:
clock (Clock)
config (FederationRateLimitConfig)
"""
self.clock = clock

self.window_size = window_size
self.sleep_limit = sleep_limit
self.sleep_sec = sleep_msec / 1000.0
self.reject_limit = reject_limit
self.concurrent_requests = concurrent_requests
self.window_size = config.window_size
self.sleep_limit = config.sleep_limit
self.sleep_sec = config.sleep_delay / 1000.0
self.reject_limit = config.reject_limit
self.concurrent_requests = config.concurrent

# request_id objects for requests which have been slept
self.sleeping_requests = set()
Expand Down
Loading