diff --git a/botgate.md b/botgate.md index 87cebc4..e5d0063 100644 --- a/botgate.md +++ b/botgate.md @@ -239,6 +239,8 @@ https://www.ip2location.com/free/visitor-blocker When downloading, select **"Apache 2.0 - 2.3 .htaccess deny"** as the output format. Other formats offered on that page (Apache 2.4, Nginx, CIDR, iptables, etc.) are not recognized by BotGate's parser. +Choose **IPv4** lists. BotGate listens on IPv4 only, so IPv6 ranges in a geo file are skipped with a warning. + IP2Location recommends refreshing these lists monthly, since IP allocations change over time. ### Performance diff --git a/botgate_proxy.py b/botgate_proxy.py index fcfdb70..2f9d730 100644 --- a/botgate_proxy.py +++ b/botgate_proxy.py @@ -415,6 +415,7 @@ def load_geo_file(path): ends) for fast binary-search containment checks. Non-'deny from' lines (comments, , order/allow directives) are ignored.""" ranges = [] + skipped_ipv6 = 0 try: with open(path, "r", errors="ignore") as f: for line in f: @@ -427,9 +428,17 @@ def load_geo_file(path): except ValueError: log.warning(f"Skipping malformed entry in {path}: {cidr!r}") continue + if net.version != 4: + # Listeners are IPv4-only, and an IPv6 range does not fit in + # the array below (OverflowError at startup). + skipped_ipv6 += 1 + continue ranges.append((int(net.network_address), int(net.broadcast_address))) except OSError as e: log.warning(f"Could not read geo file {path}: {e}") + if skipped_ipv6: + log.warning(f"Skipped {skipped_ipv6} IPv6 range(s) in {path}: " + f"BotGate listens on IPv4 only.") ranges.sort() starts = array.array("L", (r[0] for r in ranges)) diff --git a/tests/test_geo.py b/tests/test_geo.py new file mode 100644 index 0000000..b3c3f84 --- /dev/null +++ b/tests/test_geo.py @@ -0,0 +1,28 @@ +"""Geo blocklist loading.""" +from pathlib import Path +import sys +import tempfile +import unittest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +import botgate_proxy as botgate + + +class GeoFileTests(unittest.TestCase): + def test_ipv6_ranges_are_skipped_not_fatal(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "mixed.txt" + path.write_text("deny from 203.0.113.0/24\n" + "deny from 2001:db8::/32\n" + "deny from 198.51.100.0/24\n") + with self.assertLogs(botgate.log, level="WARNING") as logs: + starts, ends = botgate.load_geo_file(str(path)) + self.assertEqual(len(starts), 2) + for ip in ["203.0.113.7", "198.51.100.200"]: + self.assertTrue(botgate.geo_range_contains(int(botgate.ipaddress.ip_address(ip)), starts, ends), ip) + self.assertFalse(botgate.geo_range_contains(int(botgate.ipaddress.ip_address("192.0.2.1")), starts, ends)) + self.assertTrue(any("1 IPv6 range" in line for line in logs.output), logs.output) + + +if __name__ == "__main__": + unittest.main()