ratelimiting.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright 2014-2016 OpenMarket Ltd
  2. # Copyright 2020 The Matrix.org Foundation C.I.C.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from collections import OrderedDict
  16. from typing import Any, Optional, Tuple
  17. from synapse.api.errors import LimitExceededError
  18. from synapse.util import Clock
  19. class Ratelimiter(object):
  20. """
  21. Ratelimit actions marked by arbitrary keys.
  22. Args:
  23. clock: A homeserver clock, for retrieving the current time
  24. rate_hz: The long term number of actions that can be performed in a second.
  25. burst_count: How many actions that can be performed before being limited.
  26. """
  27. def __init__(self, clock: Clock, rate_hz: float, burst_count: int):
  28. self.clock = clock
  29. self.rate_hz = rate_hz
  30. self.burst_count = burst_count
  31. # A ordered dictionary keeping track of actions, when they were last
  32. # performed and how often. Each entry is a mapping from a key of arbitrary type
  33. # to a tuple representing:
  34. # * How many times an action has occurred since a point in time
  35. # * The point in time
  36. # * The rate_hz of this particular entry. This can vary per request
  37. self.actions = OrderedDict() # type: OrderedDict[Any, Tuple[float, int, float]]
  38. def can_do_action(
  39. self,
  40. key: Any,
  41. rate_hz: Optional[float] = None,
  42. burst_count: Optional[int] = None,
  43. update: bool = True,
  44. _time_now_s: Optional[int] = None,
  45. ) -> Tuple[bool, float]:
  46. """Can the entity (e.g. user or IP address) perform the action?
  47. Args:
  48. key: The key we should use when rate limiting. Can be a user ID
  49. (when sending events), an IP address, etc.
  50. rate_hz: The long term number of actions that can be performed in a second.
  51. Overrides the value set during instantiation if set.
  52. burst_count: How many actions that can be performed before being limited.
  53. Overrides the value set during instantiation if set.
  54. update: Whether to count this check as performing the action
  55. _time_now_s: The current time. Optional, defaults to the current time according
  56. to self.clock. Only used by tests.
  57. Returns:
  58. A tuple containing:
  59. * A bool indicating if they can perform the action now
  60. * The reactor timestamp for when the action can be performed next.
  61. -1 if rate_hz is less than or equal to zero
  62. """
  63. # Override default values if set
  64. time_now_s = _time_now_s if _time_now_s is not None else self.clock.time()
  65. rate_hz = rate_hz if rate_hz is not None else self.rate_hz
  66. burst_count = burst_count if burst_count is not None else self.burst_count
  67. # Remove any expired entries
  68. self._prune_message_counts(time_now_s)
  69. # Check if there is an existing count entry for this key
  70. action_count, time_start, _ = self.actions.get(key, (0.0, time_now_s, 0.0))
  71. # Check whether performing another action is allowed
  72. time_delta = time_now_s - time_start
  73. performed_count = action_count - time_delta * rate_hz
  74. if performed_count < 0:
  75. # Allow, reset back to count 1
  76. allowed = True
  77. time_start = time_now_s
  78. action_count = 1.0
  79. elif performed_count > burst_count - 1.0:
  80. # Deny, we have exceeded our burst count
  81. allowed = False
  82. else:
  83. # We haven't reached our limit yet
  84. allowed = True
  85. action_count += 1.0
  86. if update:
  87. self.actions[key] = (action_count, time_start, rate_hz)
  88. if rate_hz > 0:
  89. # Find out when the count of existing actions expires
  90. time_allowed = time_start + (action_count - burst_count + 1) / rate_hz
  91. # Don't give back a time in the past
  92. if time_allowed < time_now_s:
  93. time_allowed = time_now_s
  94. else:
  95. # XXX: Why is this -1? This seems to only be used in
  96. # self.ratelimit. I guess so that clients get a time in the past and don't
  97. # feel afraid to try again immediately
  98. time_allowed = -1
  99. return allowed, time_allowed
  100. def _prune_message_counts(self, time_now_s: int):
  101. """Remove message count entries that have not exceeded their defined
  102. rate_hz limit
  103. Args:
  104. time_now_s: The current time
  105. """
  106. # We create a copy of the key list here as the dictionary is modified during
  107. # the loop
  108. for key in list(self.actions.keys()):
  109. action_count, time_start, rate_hz = self.actions[key]
  110. # Rate limit = "seconds since we started limiting this action" * rate_hz
  111. # If this limit has not been exceeded, wipe our record of this action
  112. time_delta = time_now_s - time_start
  113. if action_count - time_delta * rate_hz > 0:
  114. continue
  115. else:
  116. del self.actions[key]
  117. def ratelimit(
  118. self,
  119. key: Any,
  120. rate_hz: Optional[float] = None,
  121. burst_count: Optional[int] = None,
  122. update: bool = True,
  123. _time_now_s: Optional[int] = None,
  124. ):
  125. """Checks if an action can be performed. If not, raises a LimitExceededError
  126. Args:
  127. key: An arbitrary key used to classify an action
  128. rate_hz: The long term number of actions that can be performed in a second.
  129. Overrides the value set during instantiation if set.
  130. burst_count: How many actions that can be performed before being limited.
  131. Overrides the value set during instantiation if set.
  132. update: Whether to count this check as performing the action
  133. _time_now_s: The current time. Optional, defaults to the current time according
  134. to self.clock. Only used by tests.
  135. Raises:
  136. LimitExceededError: If an action could not be performed, along with the time in
  137. milliseconds until the action can be performed again
  138. """
  139. time_now_s = _time_now_s if _time_now_s is not None else self.clock.time()
  140. allowed, time_allowed = self.can_do_action(
  141. key,
  142. rate_hz=rate_hz,
  143. burst_count=burst_count,
  144. update=update,
  145. _time_now_s=time_now_s,
  146. )
  147. if not allowed:
  148. raise LimitExceededError(
  149. retry_after_ms=int(1000 * (time_allowed - time_now_s))
  150. )