# SPDX-License-Identifier: AGPL-3.0-or-later # lint: pylint """.. _botdetection.ip_lists: Method ``ip_lists`` ------------------- The ``ip_lists`` method implements IP :py:obj:`block- ` and :py:obj:`pass-lists `. .. code:: toml [botdetection.ip_lists] pass_ip = [ '167.235.158.251', # IPv4 of check.searx.space '192.168.0.0/16', # IPv4 private network 'fe80::/10' # IPv6 linklocal ] block_ip = [ '93.184.216.34', # IPv4 of example.org '257.1.1.1', # invalid IP --> will be ignored, logged in ERROR class ] """ # pylint: disable=unused-argument from __future__ import annotations from typing import Tuple from ipaddress import ( ip_network, IPv4Address, IPv6Address, ) from . import config from ._helpers import logger logger = logger.getChild('ip_limit') SEARXNG_ORG = [ # https://github.com/searxng/searxng/pull/2484#issuecomment-1576639195 '167.235.158.251', # IPv4 check.searx.space '2a01:04f8:1c1c:8fc2::/64', # IPv6 check.searx.space ] """Passlist of IPs from the SearXNG organization, e.g. `check.searx.space`.""" def pass_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]: """Checks if the IP on the subnet is in one of the members of the ``botdetection.ip_lists.pass_ip`` list. """ if cfg.get('botdetection.ip_lists.pass_searxng_org', default=True): for net in SEARXNG_ORG: net = ip_network(net, strict=False) if real_ip.version == net.version and real_ip in net: return True, f"IP matches {net.compressed} in SEARXNG_ORG list." return ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.pass_ip', cfg) def block_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]: """Checks if the IP on the subnet is in one of the members of the ``botdetection.ip_lists.block_ip`` list. """ block, msg = ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.block_ip', cfg) if block: msg += " To remove IP from list, please contact the maintainer of the service." return block, msg def ip_is_subnet_of_member_in_list( real_ip: IPv4Address | IPv6Address, list_name: str, cfg: config.Config ) -> Tuple[bool, str]: for net in cfg.get(list_name, default=[]): try: net = ip_network(net, strict=False) except ValueError: logger.error("invalid IP %s in %s", net, list_name) continue if real_ip.version == net.version and real_ip in net: return True, f"IP matches {net.compressed} in {list_name}." return False, f"IP is not a member of an item in the f{list_name} list"