ip_lists.py 2.7 KB

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