Files
ayurishchevandClaude Sonnet 5 bcf8156085 Initial commit: RIPE CIDR/FQDN collector
Collector daemon, FastAPI server (addresses, diff, collect, sources),
SQLite storage with change journal, Docker Compose deployment,
tests, documentation and project rules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 07:29:38 +03:00

7.6 KiB
Raw Permalink Blame History

План: форматы вывода, агрегация CIDR и ip_version

Context

GET /addresses отдаёт только плоский JSON-список. Потребителям (маршрутизаторы, файрволы) приходится самим конвертировать его в конфигурацию и убирать пересекающиеся префиксы (/23 + /24). Цель: отдавать готовые конфигурации для nftables, MikroTik, BIRD и FRR, добавить агрегацию CIDR и фильтр по версии IP. Текущий ответ по умолчанию остаётся байт-в-байт прежним (старые потребители не ломаются).

Решения пользователя: форматы nftables, mikrotik, bird, frr (плюс текущий JSON); агрегация выключена по умолчанию, включается aggregate=true.

Артефакты по правилам проекта (создаются при реализации)

  • docs/plan-output-formats.md - копия этого плана (первым шагом)
  • docs/summary-output-formats.md - итоги (в конце)
  • обновить README.md (параметры, примеры интеграции)

API

GET /addresses получает параметры (существующий type=cidr|fqdn|all не меняется):

Параметр Значения По умолчанию
format json, nftables, mikrotik, bird, frr json
ip_version all, 4, 6 all
aggregate true/false false
name имя списка/набора, ^[A-Za-z][A-Za-z0-9_]{0,31}$ (защита от инъекций в конфиг) ripe

Не-JSON форматы отдаются как text/plain. Невалидные значения -> 422 (валидация FastAPI).

Изменения

1. Новый модуль formatters.py (чистые функции, без обращений к диску и сети)

  • select(values, ip_version, aggregate):
    • Путь по умолчанию (json, aggregate=false): только фильтр по версии (по наличию : в строке), сортировка как сейчас (sorted(set(...))) - вывод не меняется.
    • Иначе: разбор через ipaddress.ip_network(v, strict=False) (одиночные IP из FQDN становятся /32 и /128), невалидные значения пропускаются с logging.warning; при aggregate=true - ipaddress.collapse_addresses отдельно для v4 и v6 (после объединения источников cidr и fqdn, поэтому IP внутри префикса исчезает); сортировка по (версия, сеть).
    • Для json с агрегацией /32 и /128 печатаются как «голый» IP, как в текущем README.
  • render(fmt, v4, v6, name): каждый формат возвращает идемпотентный скрипт; пустая версия пропускается.
    • nftables (nft -f): add table inet <name>; add set inet <name> <name>_v4 { type ipv4_addr; flags interval; auto-merge; }; flush set ...; add element ... { ... } (то же для _v6, ipv6_addr). auto-merge нужен, иначе nft отвергает пересекающиеся интервалы. Другие объекты таблицы не затрагиваются.
    • MikroTik (RouterOS /import): /ip firewall address-list remove [find list=<name>] затем add list=<name> address=...; для v6 - /ipv6 firewall address-list.
    • BIRD 2 (include): define <NAME>_V4 = [ a/len, ... ]; и ..._V6 - префикс-сеты для фильтров (не статические маршруты: next-hop определяет потребитель).
    • FRR (vtysh -f): no ip prefix-list <name>_v4 затем ip prefix-list <name>_v4 seq 5|10|... permit <prefix>; для v6 - ipv6 prefix-list.
  • Все скрипты начинаются с комментарием # generated <ts>, ip_version=..., aggregate=... (в синтаксисе формата), заканчиваются переводом строки.

2. api_server.py (get_addresses)

  • Добавить enum-параметры format, ip_version, aggregate: bool, name (Query с regex).
  • Собрать множество как сейчас (get_cidrs, get_fqdn_ips), затем formatters.select и render.
  • Убрать response_model=List[str] (ответ бывает текстовым); для JSON возвращать список как раньше; для остальных - PlainTextResponse. Описать типы ответов через responses= для Swagger.
  • Ошибки хранилища по-прежнему -> 503 (существующий обработчик).

3. Документация (README.md)

  • Таблица параметров и примеры:
    • nftables: curl -s "$URL/addresses?format=nftables&ip_version=4&aggregate=true" | nft -f -
    • MikroTik: /tool fetch url="$URL/addresses?format=mikrotik" dst-path=ripe.rsc и /import ripe.rsc
    • BIRD: сохранить в файл, include, birdc configure
    • FRR: curl ... > ripe.conf && vtysh -f ripe.conf
  • Примечания: имя name определяет имена наборов; у MikroTik замена списка даёт короткое окно без записей; агрегация удаляет /32 внутри более широкого префикса.

4. Тесты (минимум 3, tests/test_formats.py, запуск в контейнере)

  1. select: по умолчанию вывод равен прежнему (сортировка, без /32), ip_version=4|6 фильтрует, aggregate=true схлопывает /23+/24 и вложенный host-IP.
  2. render: параметризованный тест по 4 форматам - ключевые строки (для v4 и v6, пустая версия пропущена).
  3. API: дефолтный /addresses возвращает JSON-список, format=mikrotik - text/plain, некорректное name -> 422 (данные подменены через cc.DATA_FILE).

Критичные файлы

Новый formatters.py; api_server.py (get_addresses); tests/test_formats.py; README.md; Dockerfile.test не меняется (COPY . . подхватит модуль).

Проверка

  1. docker build -f Dockerfile.test -t ripe-collector-test . && docker run --rm ripe-collector-test - все тесты (включая 3 прежних) проходят.
  2. На копии реальных данных: curl /addresses до и после изменений даёт идентичный ответ (сравнить diff).
  3. Вручную: format=... для каждого формата; aggregate=true уменьшает число строк; ip_version=6 не содержит IPv4.
  4. Проверка синтаксиса генерируемых конфигов в контейнерах, где возможно: BIRD (bird -p -c), nftables (nft -c -f, если хватит прав). MikroTik и FRR сверить по документации (эмулятора нет); риск: поведение no ip prefix-list для несуществующего списка в FRR - проверить по документации.