ip_limit.py 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. """.. _botdetection.ip_limit:
  2. Method ``ip_limit``
  3. -------------------
  4. The ``ip_limit`` method counts request from an IP in *sliding windows*. If
  5. there are to many requests in a sliding window, the request is evaluated as a
  6. bot request. This method requires a redis DB and needs a HTTP X-Forwarded-For_
  7. header. To take privacy only the hash value of an IP is stored in the redis DB
  8. and at least for a maximum of 10 minutes.
  9. The :py:obj:`link_token` method is used to investigate whether a request is
  10. *suspicious*. If the :py:obj:`link_token` method is activated and a request is
  11. *suspicious* the request rates are reduced:
  12. - :py:obj:`BURST_MAX` -> :py:obj:`BURST_MAX_SUSPICIOUS`
  13. - :py:obj:`LONG_MAX` -> :py:obj:`LONG_MAX_SUSPICIOUS`
  14. .. _X-Forwarded-For:
  15. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
  16. """
  17. from typing import Optional, Tuple
  18. import flask
  19. from searx.tools import config
  20. from searx import redisdb
  21. from searx import logger
  22. from searx.redislib import incr_sliding_window
  23. from . import link_token
  24. logger = logger.getChild('botdetection.ip_limit')
  25. BURST_WINDOW = 20
  26. """Time (sec) before sliding window for *burst* requests expires."""
  27. BURST_MAX = 15
  28. """Maximum requests from one IP in the :py:obj:`BURST_WINDOW`"""
  29. BURST_MAX_SUSPICIOUS = 2
  30. """Maximum of suspicious requests from one IP in the :py:obj:`BURST_WINDOW`"""
  31. LONG_WINDOW = 600
  32. """Time (sec) before the longer sliding window expires."""
  33. LONG_MAX = 150
  34. """Maximum requests from one IP in the :py:obj:`LONG_WINDOW`"""
  35. LONG_MAX_SUSPICIOUS = 10
  36. """Maximum suspicious requests from one IP in the :py:obj:`LONG_WINDOW`"""
  37. API_WONDOW = 3600
  38. """Time (sec) before sliding window for API requests (format != html) expires."""
  39. API_MAX = 4
  40. """Maximum requests from one IP in the :py:obj:`API_WONDOW`"""
  41. def filter_request(request: flask.Request, cfg: config.Config) -> Optional[Tuple[int, str]]:
  42. redis_client = redisdb.client()
  43. x_forwarded_for = request.headers.get('X-Forwarded-For', '')
  44. if not x_forwarded_for:
  45. logger.error("missing HTTP header X-Forwarded-For")
  46. if request.args.get('format', 'html') != 'html':
  47. c = incr_sliding_window(redis_client, 'IP limit - API_WONDOW:' + x_forwarded_for, API_WONDOW)
  48. if c > API_MAX:
  49. return 429, "BLOCK %s: API limit exceeded"
  50. suspicious = False
  51. if cfg['botdetection.ip_limit.link_token']:
  52. suspicious = link_token.is_suspicious(request)
  53. if suspicious:
  54. c = incr_sliding_window(redis_client, 'IP limit - BURST_WINDOW:' + x_forwarded_for, BURST_WINDOW)
  55. if c > BURST_MAX_SUSPICIOUS:
  56. return 429, f"bot detected, too many request from {x_forwarded_for} in BURST_MAX_SUSPICIOUS"
  57. c = incr_sliding_window(redis_client, 'IP limit - LONG_WINDOW:' + x_forwarded_for, LONG_WINDOW)
  58. if c > LONG_MAX_SUSPICIOUS:
  59. return 429, f"bot detected, too many request from {x_forwarded_for} in LONG_MAX_SUSPICIOUS"
  60. else:
  61. c = incr_sliding_window(redis_client, 'IP limit - BURST_WINDOW:' + x_forwarded_for, BURST_WINDOW)
  62. if c > BURST_MAX:
  63. return 429, f"bot detected, too many request from {x_forwarded_for} in BURST_MAX"
  64. c = incr_sliding_window(redis_client, 'IP limit - LONG_WINDOW:' + x_forwarded_for, LONG_WINDOW)
  65. if c > LONG_MAX:
  66. return 429, f"bot detected, too many request from {x_forwarded_for} in LONG_MAX"
  67. return None