Этот Python-скрипт схлопывает список CIDR-блоков в минимальный эквивалентный набор — объединяет смежные и убирает пересекающиеся префиксы через ipaddress.collapse_addresses из стандартной библиотеки — и сообщает, сколько адресов покрывает список. Он не делает сетевых запросов: агрегация уже имеющегося списка CIDR — чисто локальная теоретико-множественная операция.
Зависимости: нет — только стандартная библиотека Python 3.8+ (urllib, json, csv, ipaddress, argparse). pip install не требуется.
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""cidr-aggregate.py -- collapse a list of CIDR blocks into the smallest
equivalent set, and count the addresses they cover.
This recipe does NOT call the ip.xakki.pro API -- collapsing/aggregating a
CIDR list you already have is pure local set arithmetic
(ipaddress.collapse_addresses from the standard library), the same honest
"no API needed here" case as subnet-membership.js in this section.
Reads a text file with one CIDR block per line (IPv4 and IPv6 may be mixed
freely -- each family is collapsed separately, since a v4 prefix and a v6
prefix can never overlap or merge). Prints the minimal equivalent CIDR set
per family, plus how many individual addresses the input covers before and
after collapsing (collapsing removes redundant overlap, it never changes
the covered address count).
Zero dependencies -- stdlib only (ipaddress, argparse). Python 3.8+.
Usage:
python3 cidr-aggregate.py prefixes.txt
"""
import argparse
import ipaddress
import sys
def main():
parser = argparse.ArgumentParser(description="Collapse a list of CIDR blocks into the minimal equivalent set.")
parser.add_argument("input_file", help="Path to a text file, one CIDR block per line.")
args = parser.parse_args()
v4_networks, v6_networks = [], []
errors = []
with open(args.input_file, encoding="utf-8") as f:
for line_no, raw_line in enumerate(f, start=1):
line = raw_line.strip()
if not line or line.startswith("#"):
continue
try:
# strict=False: "10.1.2.3/24" is accepted and normalized to
# "10.1.2.0/24" rather than rejected for having host bits set
# -- this recipe aggregates real-world lists, which routinely
# contain that shape.
network = ipaddress.ip_network(line, strict=False)
except ValueError as e:
errors.append("line {}: {!r} -- {}".format(line_no, line, e))
continue
(v6_networks if network.version == 6 else v4_networks).append(network)
if errors:
print("skipped {} malformed line(s):".format(len(errors)), file=sys.stderr)
for err in errors:
print(" " + err, file=sys.stderr)
for family, networks in (("IPv4", v4_networks), ("IPv6", v6_networks)):
if not networks:
continue
collapsed = list(ipaddress.collapse_addresses(networks))
addresses_before = sum(n.num_addresses for n in networks)
addresses_after = sum(n.num_addresses for n in collapsed)
print("\n{}: {} input block(s) -> {} collapsed block(s)".format(family, len(networks), len(collapsed)))
print(" addresses covered: {} (collapsing never changes this total, "
"it only removes redundant overlap)".format(addresses_after))
if addresses_before != addresses_after:
# Only possible if the input itself double-counted overlapping
# ranges before collapsing folded them together.
print(" (raw sum of individual input blocks was {}, {} of which was overlap)".format(
addresses_before, addresses_before - addresses_after))
for network in collapsed:
print(" " + str(network))
if not v4_networks and not v6_networks:
print("no valid CIDR blocks found in {}".format(args.input_file), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Скрипт распространяется по лицензии MIT (заголовок SPDX-License-Identifier: MIT в начале файла) — копируйте, изменяйте и используйте свободно в своём проекте, указывать авторство не обязательно.
Записи IPv4 и IPv6 схлопываются ОТДЕЛЬНО и никогда не объединяются друг с другом — префикс v4 и префикс v6 занимают совершенно разные адресные пространства, поэтому скрипт сначала распределяет вход по семействам, а затем вызывает collapse_addresses отдельно для каждого. «10.1.2.3/24» принимается (strict=False) и нормализуется до «10.1.2.0/24», а не отклоняется из-за установленных битов хоста, поскольку реальные экспортированные списки префиксов регулярно содержат такую запись.
Схлопывание никогда не меняет общее число покрытых адресов — оно только убирает избыточное пересечение (/24, полностью входящий в /16 из того же списка, не добавляет ничего нового) и объединяет ровно смежные блоки одинакового размера в общий родительский префикс (два /24, вместе образующие ровный /23). Скрипт выводит и число блоков после схлопывания, и общее число адресов, а также отдельно сообщает, какая часть исходной суммы входа была чистым пересечением, чтобы было видно, что именно и почему уменьшилось.
Некорректные строки собираются в список ошибок ВМЕСТЕ с номером строки, а не прерывают весь прогон — одна опечатка в экспорте на 10 000 строк не должна стоить вам результатов по остальным 9999. Пустые строки и строки, начинающиеся с «#», молча трактуются как комментарии/пробелы, а не ошибки, так что скрипт принимает те же слегка аннотированные файлы списков префиксов, которые реально хранятся в системах контроля версий.
Этот скрипт не делает сетевых запросов и не имеет эквивалента в API — вся задача локальные вычисления/разбор уже имеющихся данных, отвечать здесь нашему (или любому) API не на что. Если нужны ещё и живые факты об IP (геолокация, ASN, признаки угроз/фрода) — это описано в документации API:
Понравился инструмент? Бесплатный аккаунт нашего API даёт 1000 запросов в день.Смотреть документацию API →