Make all the rate limiting options more consistent (#5181)

This commit is contained in:
Amber Brown 2019-05-15 12:06:04 -05:00 committed by GitHub
parent 5f027a315f
commit f1e5b41388
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 131 deletions

1
changelog.d/5181.feature Normal file
View file

@ -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.

View file

@ -446,21 +446,15 @@ log_config: "CONFDIR/SERVERNAME.log.config"
## Ratelimiting ## ## Ratelimiting ##
# Number of messages a client can send per second # Ratelimiting settings for client actions (registration, login, messaging).
#
#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.
# #
# Each ratelimiting configuration is made of two parameters: # Each ratelimiting configuration is made of two parameters:
# - per_second: number of requests a client can send per second. # - per_second: number of requests a client can send per second.
# - burst_count: number of requests a client can send before being throttled. # - burst_count: number of requests a client can send before being throttled.
# #
# Synapse currently uses the following configurations: # 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 # - one for registration that ratelimits registration requests based on the
# client's IP address. # client's IP address.
# - one for login that ratelimits login requests based on the client's IP # - one for login that ratelimits login requests based on the client's IP
@ -473,6 +467,10 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# #
# The defaults are as shown below. # The defaults are as shown below.
# #
#rc_message:
# per_second: 0.2
# burst_count: 10
#
#rc_registration: #rc_registration:
# per_second: 0.17 # per_second: 0.17
# burst_count: 3 # burst_count: 3
@ -488,29 +486,28 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# per_second: 0.17 # per_second: 0.17
# burst_count: 3 # 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 # Ratelimiting settings for incoming federation
# before the server will delay processing the request.
# #
#federation_rc_sleep_limit: 10 # The rc_federation configuration is made up of the following settings:
# - window_size: window size in milliseconds
# The duration in milliseconds to delay processing events from # - sleep_limit: number of federation requests from a single server in
# remote servers by if they go over the sleep limit. # 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_sleep_delay: 500 # The defaults are as shown below.
# The maximum number of concurrent federation requests allowed
# from a single server
# #
#federation_rc_reject_limit: 50 #rc_federation:
# window_size: 1000
# The number of federation requests to concurrently process from a # sleep_limit: 10
# single server # sleep_delay: 500
# # reject_limit: 50
#federation_rc_concurrent: 3 # concurrent: 3
# Target outgoing federation transaction frequency for sending read-receipts, # Target outgoing federation transaction frequency for sending read-receipts,
# per-room. # per-room.

View file

@ -16,16 +16,56 @@ from ._base import Config
class RateLimitConfig(object): class RateLimitConfig(object):
def __init__(self, config): def __init__(self, config, defaults={"per_second": 0.17, "burst_count": 3.0}):
self.per_second = config.get("per_second", 0.17) self.per_second = config.get("per_second", defaults["per_second"])
self.burst_count = config.get("burst_count", 3.0) self.burst_count = config.get("burst_count", defaults["burst_count"])
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): class RatelimitConfig(Config):
def read_config(self, 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", {})) self.rc_registration = RateLimitConfig(config.get("rc_registration", {}))
@ -33,38 +73,26 @@ class RatelimitConfig(Config):
self.rc_login_address = RateLimitConfig(rc_login_config.get("address", {})) self.rc_login_address = RateLimitConfig(rc_login_config.get("address", {}))
self.rc_login_account = RateLimitConfig(rc_login_config.get("account", {})) self.rc_login_account = RateLimitConfig(rc_login_config.get("account", {}))
self.rc_login_failed_attempts = RateLimitConfig( 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( 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): def default_config(self, **kwargs):
return """\ return """\
## Ratelimiting ## ## Ratelimiting ##
# Number of messages a client can send per second # Ratelimiting settings for client actions (registration, login, messaging).
#
#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.
# #
# Each ratelimiting configuration is made of two parameters: # Each ratelimiting configuration is made of two parameters:
# - per_second: number of requests a client can send per second. # - per_second: number of requests a client can send per second.
# - burst_count: number of requests a client can send before being throttled. # - burst_count: number of requests a client can send before being throttled.
# #
# Synapse currently uses the following configurations: # 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 # - one for registration that ratelimits registration requests based on the
# client's IP address. # client's IP address.
# - one for login that ratelimits login requests based on the client's IP # - one for login that ratelimits login requests based on the client's IP
@ -77,6 +105,10 @@ class RatelimitConfig(Config):
# #
# The defaults are as shown below. # The defaults are as shown below.
# #
#rc_message:
# per_second: 0.2
# burst_count: 10
#
#rc_registration: #rc_registration:
# per_second: 0.17 # per_second: 0.17
# burst_count: 3 # burst_count: 3
@ -92,29 +124,28 @@ class RatelimitConfig(Config):
# per_second: 0.17 # per_second: 0.17
# burst_count: 3 # 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 # Ratelimiting settings for incoming federation
# before the server will delay processing the request.
# #
#federation_rc_sleep_limit: 10 # The rc_federation configuration is made up of the following settings:
# - window_size: window size in milliseconds
# The duration in milliseconds to delay processing events from # - sleep_limit: number of federation requests from a single server in
# remote servers by if they go over the sleep limit. # 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_sleep_delay: 500 # The defaults are as shown below.
# The maximum number of concurrent federation requests allowed
# from a single server
# #
#federation_rc_reject_limit: 50 #rc_federation:
# window_size: 1000
# The number of federation requests to concurrently process from a # sleep_limit: 10
# single server # sleep_delay: 500
# # reject_limit: 50
#federation_rc_concurrent: 3 # concurrent: 3
# Target outgoing federation transaction frequency for sending read-receipts, # Target outgoing federation transaction frequency for sending read-receipts,
# per-room. # per-room.

View file

@ -63,11 +63,7 @@ class TransportLayerServer(JsonResource):
self.authenticator = Authenticator(hs) self.authenticator = Authenticator(hs)
self.ratelimiter = FederationRateLimiter( self.ratelimiter = FederationRateLimiter(
self.clock, self.clock,
window_size=hs.config.federation_rc_window_size, config=hs.config.rc_federation,
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,
) )
self.register_servlets() self.register_servlets()

View file

@ -90,8 +90,8 @@ class BaseHandler(object):
messages_per_second = override.messages_per_second messages_per_second = override.messages_per_second
burst_count = override.burst_count burst_count = override.burst_count
else: else:
messages_per_second = self.hs.config.rc_messages_per_second messages_per_second = self.hs.config.rc_message.per_second
burst_count = self.hs.config.rc_message_burst_count burst_count = self.hs.config.rc_message.burst_count
allowed, time_allowed = self.ratelimiter.can_do_action( allowed, time_allowed = self.ratelimiter.can_do_action(
user_id, time_now, user_id, time_now,

View file

@ -31,6 +31,7 @@ from synapse.api.errors import (
SynapseError, SynapseError,
UnrecognizedRequestError, UnrecognizedRequestError,
) )
from synapse.config.ratelimiting import FederationRateLimitConfig
from synapse.config.server import is_threepid_reserved from synapse.config.server import is_threepid_reserved
from synapse.http.servlet import ( from synapse.http.servlet import (
RestServlet, RestServlet,
@ -153,16 +154,18 @@ class UsernameAvailabilityRestServlet(RestServlet):
self.registration_handler = hs.get_registration_handler() self.registration_handler = hs.get_registration_handler()
self.ratelimiter = FederationRateLimiter( self.ratelimiter = FederationRateLimiter(
hs.get_clock(), hs.get_clock(),
# Time window of 2s FederationRateLimitConfig(
window_size=2000, # Time window of 2s
# Artificially delay requests if rate > sleep_limit/window_size window_size=2000,
sleep_limit=1, # Artificially delay requests if rate > sleep_limit/window_size
# Amount of artificial delay to apply sleep_limit=1,
sleep_msec=1000, # Amount of artificial delay to apply
# Error with 429 if more than reject_limit requests are queued sleep_msec=1000,
reject_limit=1, # Error with 429 if more than reject_limit requests are queued
# Allow 1 request at a time reject_limit=1,
concurrent_requests=1, # Allow 1 request at a time
concurrent_requests=1,
)
) )
@defer.inlineCallbacks @defer.inlineCallbacks

View file

@ -30,31 +30,14 @@ logger = logging.getLogger(__name__)
class FederationRateLimiter(object): class FederationRateLimiter(object):
def __init__(self, clock, window_size, sleep_limit, sleep_msec, def __init__(self, clock, config):
reject_limit, concurrent_requests):
""" """
Args: Args:
clock (Clock) clock (Clock)
window_size (int): The window size in milliseconds. config (FederationRateLimitConfig)
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.
""" """
self.clock = clock self.clock = clock
self._config = config
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.ratelimiters = {} self.ratelimiters = {}
def ratelimit(self, host): def ratelimit(self, host):
@ -76,25 +59,25 @@ class FederationRateLimiter(object):
host, host,
_PerHostRatelimiter( _PerHostRatelimiter(
clock=self.clock, clock=self.clock,
window_size=self.window_size, config=self._config,
sleep_limit=self.sleep_limit,
sleep_msec=self.sleep_msec,
reject_limit=self.reject_limit,
concurrent_requests=self.concurrent_requests,
) )
).ratelimit() ).ratelimit()
class _PerHostRatelimiter(object): class _PerHostRatelimiter(object):
def __init__(self, clock, window_size, sleep_limit, sleep_msec, def __init__(self, clock, config):
reject_limit, concurrent_requests): """
Args:
clock (Clock)
config (FederationRateLimitConfig)
"""
self.clock = clock self.clock = clock
self.window_size = window_size self.window_size = config.window_size
self.sleep_limit = sleep_limit self.sleep_limit = config.sleep_limit
self.sleep_sec = sleep_msec / 1000.0 self.sleep_sec = config.sleep_delay / 1000.0
self.reject_limit = reject_limit self.reject_limit = config.reject_limit
self.concurrent_requests = concurrent_requests self.concurrent_requests = config.concurrent
# request_id objects for requests which have been slept # request_id objects for requests which have been slept
self.sleeping_requests = set() self.sleeping_requests = set()

View file

@ -134,10 +134,6 @@ def default_config(name, parse=False):
"email_enable_notifs": False, "email_enable_notifs": False,
"block_non_admin_invites": False, "block_non_admin_invites": False,
"federation_domain_whitelist": None, "federation_domain_whitelist": None,
"federation_rc_reject_limit": 10,
"federation_rc_sleep_limit": 10,
"federation_rc_sleep_delay": 100,
"federation_rc_concurrent": 10,
"filter_timeline_limit": 5000, "filter_timeline_limit": 5000,
"user_directory_search_all_users": False, "user_directory_search_all_users": False,
"user_consent_server_notice_content": None, "user_consent_server_notice_content": None,
@ -156,8 +152,13 @@ def default_config(name, parse=False):
"mau_stats_only": False, "mau_stats_only": False,
"mau_limits_reserved_threepids": [], "mau_limits_reserved_threepids": [],
"admin_contact": None, "admin_contact": None,
"rc_messages_per_second": 10000, "rc_federation": {
"rc_message_burst_count": 10000, "reject_limit": 10,
"sleep_limit": 10,
"sleep_delay": 10,
"concurrent": 10,
},
"rc_message": {"per_second": 10000, "burst_count": 10000},
"rc_registration": {"per_second": 10000, "burst_count": 10000}, "rc_registration": {"per_second": 10000, "burst_count": 10000},
"rc_login": { "rc_login": {
"address": {"per_second": 10000, "burst_count": 10000}, "address": {"per_second": 10000, "burst_count": 10000},
@ -375,12 +376,7 @@ def register_federation_servlets(hs, resource):
resource=resource, resource=resource,
authenticator=federation_server.Authenticator(hs), authenticator=federation_server.Authenticator(hs),
ratelimiter=FederationRateLimiter( ratelimiter=FederationRateLimiter(
hs.get_clock(), hs.get_clock(), config=hs.config.rc_federation
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,
), ),
) )