ip_lists.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. """.. _botdetection.ip_lists:
  3. Method ``ip_lists``
  4. -------------------
  5. The ``ip_lists`` method implements IP :py:obj:`block- <block_ip>` and
  6. :py:obj:`pass-lists <pass_ip>`.
  7. .. code:: toml
  8. [botdetection.ip_lists]
  9. pass_ip = [
  10. '167.235.158.251', # IPv4 of check.searx.space
  11. '192.168.0.0/16', # IPv4 private network
  12. 'fe80::/10' # IPv6 linklocal
  13. ]
  14. block_ip = [
  15. '93.184.216.34', # IPv4 of example.org
  16. '257.1.1.1', # invalid IP --> will be ignored, logged in ERROR class
  17. ]
  18. """
  19. # pylint: disable=unused-argument
  20. from __future__ import annotations
  21. from typing import Tuple
  22. from ipaddress import (
  23. ip_network,
  24. IPv4Address,
  25. IPv6Address,
  26. )
  27. from . import config
  28. from ._helpers import logger
  29. logger = logger.getChild('ip_limit')
  30. SEARXNG_ORG = [
  31. # https://github.com/searxng/searxng/pull/2484#issuecomment-1576639195
  32. '167.235.158.251', # IPv4 check.searx.space
  33. '2a01:04f8:1c1c:8fc2::/64', # IPv6 check.searx.space
  34. ]
  35. """Passlist of IPs from the SearXNG organization, e.g. `check.searx.space`."""
  36. def pass_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]:
  37. """Checks if the IP on the subnet is in one of the members of the
  38. ``botdetection.ip_lists.pass_ip`` list.
  39. """
  40. if cfg.get('botdetection.ip_lists.pass_searxng_org', default=True):
  41. for net in SEARXNG_ORG:
  42. net = ip_network(net, strict=False)
  43. if real_ip.version == net.version and real_ip in net:
  44. return True, f"IP matches {net.compressed} in SEARXNG_ORG list."
  45. return ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.pass_ip', cfg)
  46. def block_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]:
  47. """Checks if the IP on the subnet is in one of the members of the
  48. ``botdetection.ip_lists.block_ip`` list.
  49. """
  50. block, msg = ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.block_ip', cfg)
  51. if block:
  52. msg += " To remove IP from list, please contact the maintainer of the service."
  53. return block, msg
  54. def ip_is_subnet_of_member_in_list(
  55. real_ip: IPv4Address | IPv6Address, list_name: str, cfg: config.Config
  56. ) -> Tuple[bool, str]:
  57. for net in cfg.get(list_name, default=[]):
  58. try:
  59. net = ip_network(net, strict=False)
  60. except ValueError:
  61. logger.error("invalid IP %s in %s", net, list_name)
  62. continue
  63. if real_ip.version == net.version and real_ip in net:
  64. return True, f"IP matches {net.compressed} in {list_name}."
  65. return False, f"IP is not a member of an item in the f{list_name} list"