Files
ripe-cidr-collector/docs/plan-output-formats.md
T

65 lines
7.6 KiB
Markdown
Raw Normal View History

2026-09-21 07:29:38 +03:00
# План: форматы вывода, агрегация 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 - проверить по документации.