# 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_`). - `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`.