Files
elmprodvpn/docs/phase-e/E1_MULTI_CLIENT_PBR_DESIGN.md

11 KiB
Raw Permalink Blame History

E1 Дизайн multi-client маршрутизации (PBR)

Дата: 2026-03-04 Статус: draft Владелец: Engineering

1) Цель

  • Спроектировать расширение текущего ядра так, чтобы несколько transport-клиентов (sing-box, dnstt-client, phoenix->slipstream) работали под единым control-plane API.
  • Сохранить один источник истины для маршрутизации: только Go-ядро управляет PBR/nft/ip rule, UI только вызывает API.
  • Обеспечить безопасную многоклиентную схему без конфликтов: один трафик/сайт не должен одновременно идти через два интерфейса.

2) Текущий baseline (по коду)

  • Сейчас модель маршрутизации бинарная: vpn|direct + глобальные MARK/MARK_APP/MARK_DIRECT/MARK_INGRESS.
  • Runtime app routing хранится в traffic-appmarks.json, persistent launcher-профили в traffic-app-profiles.json.
  • Центр оркестрации: traffic_mode.go, traffic_appmarks.go, routes_update.go.
  • Ограничение: нет сущности "клиент-транспорт" как объекта с собственным iface/table/mark.

3) Архитектурный инвариант

  • PBR Engine в Go-ядре остается единственным writer для:
    • ip rule,
    • ip route table,
    • nft chains/sets/rules,
    • conntrack mark policy.
  • Transport backends (sing-box/dnstt/phoenix) подключаются через backend-адаптеры и не пишут маршруты напрямую.
  • UI (desktop/web/iOS/android) оперирует одинаковым API-контрактом, не знает о внутреннем устройстве backend-клиентов.

4) Целевая модель данных

4.1 TransportClient

  • id: стабильный ключ (sb-main, dnstt-home, phoenix-eu).
  • kind: singbox | dnstt | phoenix.
  • enabled: bool.
  • status: starting | up | degraded | down.
  • iface: фактический интерфейс/туннель.
  • routing_table: имя таблицы (agvpn_<id>).
  • mark_hex: выделенная fwmark клиента.
  • priority_base: базовый диапазон ip rule pref для клиента.
  • capabilities: tcp, udp, dns_tunnel, ssh_tunnel.
  • health: last_check, latency, last_error.

4.2 RouteIntent

  • Нормализованная запись назначения трафика к клиенту:
    • selector_type: domain | cidr | app_key | cgroup | uid.
    • selector_value: значение селектора.
    • client_id: целевой клиент.
    • priority: порядок применения.
    • mode: strict | fallback.

4.3 ConflictRecord

  • Запись о конфликте маршрутизации:
    • key: нормализованный ключ пересечения.
    • owners: список client_id.
    • severity: warn | block.
    • reason: человеко-читаемая причина.
    • suggested_resolution: автоматическая подсказка.

5) Схема марков и таблиц (без конфликтов)

5.1 Mark allocator

  • Ввести менеджер выделения марков из пула, например:
    • 0x100-0x1FF для client-specific route marks,
    • 0x66/0x67/0x68/0x69 оставить для legacy/системных сценариев.
  • Для каждого client_id выделяется:
    • client_mark,
    • client_reply_mark (если нужен отдельный ingress stickiness).

5.2 Table allocator

  • Для каждого клиента заводится отдельная routing table:
    • agvpn_sb_main, agvpn_dnstt_home, agvpn_phoenix_eu.
  • В таблице только default route через iface клиента + локальные bypass-правила.

5.3 Rule priority allocator

  • Для каждого клиента выделяется непересекаемый диапазон pref:
    • пример: 13000-13049 клиент A, 13050-13099 клиент B.
  • Это исключает перетирание правил между клиентами при apply/reconcile.

6) Защита от "мешанины" трафика

6.1 Destination ownership lock

  • Один домен/cidr/app_key в активной конфигурации может иметь только одного владельца (client_id).
  • При пересечении:
    • по умолчанию block (HTTP 409 на apply),
    • опционально force_override с явным подтверждением пользователя.

6.2 Flow stickiness (conntrack)

  • Для первого пакета потока проставляется ct mark = client_mark.
  • Для последующих пакетов mark восстанавливается из ct mark, чтобы один и тот же flow не перескакивал между интерфейсами.
  • Правило действует в output и prerouting, аналогично текущему ingress-reply подходу.

6.3 DNS/IP coherence

  • Для domain-based маршрутизации вводится owner-cache:
    • domain -> client_id -> ip set с TTL.
  • Один и тот же домен в активной политике не может одновременно резолвиться в разные клиентские set-цепочки.

6.4 Audit/guardrail

  • Расширить traffic_audit на multi-client проверки:
    • duplicate destination ownership,
    • overlap CIDR между клиентами,
    • app_key на двух клиентах одновременно,
    • nft/rule drift по client chains.

7) UX дизайн (удобное добавление/переключение)

7.1 Экран "Клиенты"

  • Список клиентов: имя, тип, статус, интерфейс, health.
  • Действия: Добавить, Включить/Выключить, Перезапустить, Удалить.
  • Мастер добавления:
    • Шаг 1: тип клиента (sing-box, dnstt, phoenix),
    • Шаг 2: параметры подключения,
    • Шаг 3: health-check,
    • Шаг 4: назначение default policy.
  • Подпункты UX для engine:
    • единый селектор Active engine с вариантами singbox|dnstt|phoenix;
    • быстрые действия Connect, Disconnect, Switch to ...;
    • явное отображение desired_engine vs active_engine;
    • при деградации показывать last_error и action Rollback to previous engine.

7.2 Экран "Маршрутизация"

  • Матрица Селектор -> Клиент.
  • Массовое назначение списков IP/CIDR/доменов.
  • Быстрый переключатель "перенести селектор на другой клиент" с dry-run проверкой конфликтов.

7.3 UX предупреждения

  • Перед apply показывать diff:
    • какие селекторы сменят владельца,
    • какие потоки могут быть прерваны,
    • какие конфликты заблокируют применение.
  • При force_override обязательное подтверждение пользователя с явным риском:
    • "Один и тот же сайт может потерять стабильность при частой смене интерфейса".
  • При switch/connect engine:
    • показывать предупреждение о кратковременном разрыве активных сессий;
    • запрещать параллельные mutating-операции до завершения текущего switch;
    • при failed switch предлагать rollback на предыдущий engine.

8) API-контракт (новые ручки, проект)

  • GET /api/v1/transport/clients
  • POST /api/v1/transport/clients
  • POST /api/v1/transport/clients/{id}/start
  • POST /api/v1/transport/clients/{id}/stop
  • GET /api/v1/transport/clients/{id}/health
  • GET /api/v1/transport/policies
  • POST /api/v1/transport/policies/validate
  • POST /api/v1/transport/policies/apply
  • GET /api/v1/transport/conflicts
  • GET /api/v1/transport/capabilities

Принцип:

  • Все операции изменения policy идут через validate -> apply.
  • apply атомарный: либо вся новая политика применена, либо rollback на предыдущую snapshot-конфигурацию.

9) Реализация по шагам

E1.1 Контракты и состояние

  • Ввести state-файлы:
    • transport-clients.json,
    • transport-policies.json,
    • transport-conflicts.json.
  • Добавить DTO и минимальные read endpoints.

E1.2 PBR compiler v2

  • Реализовать компиляцию RouteIntent в:
    • nft sets/chains per client,
    • ip rule pref ranges per client,
    • table route entries per client.

E1.3 Guardrails

  • Валидация ownership/overlap до apply.
  • Conntrack stickiness rules для стабильности flow.

E1.4 UX-ready слой

  • API предупреждений + dry-run diff.
  • SSE события:
    • transport_client_state_changed,
    • transport_policy_applied,
    • transport_conflict_detected.

10) Критерии готовности дизайна

  • Можно добавить 2+ клиентов и поднять 2+ интерфейса без перезаписи чужих rule/table.
  • Нельзя назначить один и тот же selector двум клиентам без explicit override.
  • traffic_audit показывает целостную картину конфликтов и drift.
  • UI получает понятные предупреждения до применения рискованной конфигурации.

11) Обратная совместимость

  • Текущие /api/v1/traffic/* продолжают работать в legacy-режиме.
  • При отсутствии multi-client политики ядро использует текущий single-client pipeline.
  • Миграция: legacy vpn|direct может быть автоматически представлена как:
    • client_id=legacy-vpn,
    • client_id=legacy-direct.