diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index d932bc70..7c46ebbc 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -29,9 +29,17 @@ utun_instance_destroy(inst); Desktop подтверждает start только после готовности всех сервисов и uasync poll; ошибка запрошенного TUN откатывает запуск и возвращается в GUI. Android сохраняет тот же выбор при рестарте. Повторный start работающего сервиса и повторный stop допустимы. Ошибка старта -сервиса откатывает созданные им ресурсы. `utun_instance_stop()` останавливает цикл +UTUN/чата откатывает созданные им ресурсы; ошибка core start оставляет подготовленные +ресурсы для retry/destroy. `utun_instance_init()` сохраняет ранее запущенные сервисы +при отказе последующего. `utun_instance_stop()` останавливает цикл приложения; освобождение ядра выполняет `utun_instance_destroy()`. +Create уже подготавливает listeners, топологию/SQLite, router/proxy и мониторинг; +core/UTUN/chat запускаются отдельно. `create_from_str` использует временный файл +в `/tmp`, updater и parse. `create_from_config` передаёт владение config только +при успехе; при отказе config остаётся caller, но node_id мог быть исправлен. +Logger общий для процесса, его изменения не откатываются при отказе создания. + Для TUN в desktop `vibechat.cfg` задаются те же параметры, что и у CLI: ```ini @@ -83,7 +91,14 @@ UTUN stop закрывает свои групповые запросы, но с окончательный CLOSED запускает повтор через секунду. Удаление записи отменяет её повтор и освобождает только её handles. Ошибка подготовки нового списка сохраняет прежний список. Изменение автосокетов переприменяет сохранённые endpoints также для bootstrap без подписанного NODEINFO. -Полный reload сохраняет набор запущенных сервисов; частичный обновляет `[client]` также без UTUN. +Полный reload сохраняет набор запущенных сервисов и running; частичный обновляет `[client]` также без UTUN. +Restart нужен при смене негорячих global-настроек (в том числе db_path, control, +TUN/NAT/proxy), подсетей либо server-конфига в automatic режиме. В ручном режиме +сравниваются все параметры endpoint и IPv6 scope; неизменённый port=0 сохраняет +порт ОС. TCP endpoint закрывается вместе с listener и своей TCP-моделью. +Ошибка подготовки сохраняет прежний instance; ошибка создания после полного destroy +возвращает NULL. Частичный reload не атомарен: отказ нового endpoint логируется, +новый config сохраняется для повторной попытки. Caller всегда использует результат reload. Proxy настраивается при создании ядра. Instance владеет общими UDP/ICMP-контекстами; TCP client/server владеют только своими bindings, listeners и TCP-потоками. @@ -98,6 +113,16 @@ UTUN/CHAT-групп и транспорта. Waiters/таймеры снима SQLite открывается ядром один раз: `db_path/chats.db`, либо `:memory:`, если путь не задан. Остановка чата сохраняет каналы и сообщения в этой БД. В варианте `:memory:` данные сохраняются только до уничтожения ядра. +Caller завершает заимствованные SQLite statements/backups до destroy. Ошибка close +логируется; close_v2 при BUSY откладывает освобождение БД до завершения этих объектов. +UASYNC живёт до конца destroy. Отменённые heap-таймеры могут оставаться в нём +до следующего poll; это не разрешает callback обращаться к освобождённому instance. + +Headless chat/call/radio bind задаёт IPv4 literal:port, decimal 0..65535; +port=0 выбирает ОС. Ошибка bind/listener не останавливает чат и не открывает +порт по умолчанию. Stop снимает клиентов с UASYNC, отменяет cleanup/mix-таймеры, +освобождает headless-контексты и кодеки. PCM после 11-байтового заголовка +копируется в выровненный буфер перед Opus. Работающий общий транспорт сохраняется, пока он нужен хотя бы одному из пиров. Закрытие запроса одной группы не разрывает соединение, нужное другой группе. При остановке группы отменяются @@ -436,3 +461,10 @@ standby даже при начальном `client_activity=STANDBY`. Keepalive handshake. Пробуждение по таймеру и внешней сети сначала сообщает ACTIVE-окно пирам, затем запускает ожидающие задачи. При выходе сначала восстанавливаются линки; deinit отменяет ожидающие задачи и очищает подписки старого ядра. + +Activity/peer-sleep подписки уникальны по fn+arg и вызываются синхронно в обратном +порядке добавления. Remove себя/соседа безопасен: память записи освобождается +после внешней рассылки. Новые подписки могут получить вложенное событие; +более новая эффективная activity прерывает остаток прежней рассылки. +Destroy/reload из этих callbacks запрещён. `test_instance_contract` проверяет +вложенные подписки, отказы ресурсов, manual/full reload, TCP listener и headless stop/start. diff --git a/src/utun_instance_doc.md b/src/utun_instance_doc.md index a8704336..666cd23d 100644 --- a/src/utun_instance_doc.md +++ b/src/utun_instance_doc.md @@ -1,112 +1,183 @@ -# utun_instance — Корневой инстанс (центральный оркестратор) +# utun_instance — ядро одного узла и независимые сервисы -## 1. Назначение +Полностью сверены `utun_instance.c/.h` и этот документ, 26.10.2026. +Связанный документ [service_lifecycle.md](../doc/service_lifecycle.md) описывает также GUI, +Android и media workers; аудит instance не означает завершения всех его разделов. -`UTUN_INSTANCE` — **центральная структура всего процесса uTun**. Владеет всеми подсистемами: TUN, таблица маршрутов, BGP-группы, ETCP-сокеты/соединения, control-сервер, message transport, firewall, NAT, TCP-прокси, memory pools, NTP, синхронизация БД, менеджер соединений. +## Использование и поток -Это **единственная точка входа для инициализации и завершения** всех компонентов. Порядок инициализации и destroy строго определён — от низкоуровневых подсистем к высокоуровневым и обратно. +`UTUN_INSTANCE` принадлежит одному узлу. Несколько экземпляров могут разделять +переданный `UASYNC`; он принадлежит вызывающему и должен жить до конца destroy. +Все операции выполняются в его потоке. Stop сервиса, reload и destroy выполняются +вне callbacks останавливаемых модулей. Межпоточные команды передаются в uasync; +прямые конкурентные обращения к instance недопустимы. -## 2. Как пользоваться - -### Создание ```c struct UASYNC* ua = uasync_create(); -struct UTUN_INSTANCE* inst = utun_instance_create(ua, "utun.conf"); // из файла -// или -struct UTUN_INSTANCE* inst = utun_instance_create_from_config(ua, config); // из готовой структуры -// или -struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config_text); // из строки (тесты) -``` - -### Инициализация (оркестрация) -```c -utun_instance_init(inst); // запускает всю цепочку инициализации -``` -`utun_instance_init()` **не вызывает** `utun_instance_create()` — create только аллоцирует память и загружает конфиг. `init` запускает все подсистемы в строгом порядке: -1. `db_sync_init` — распределённая БД с репликацией -2. `routing_set_tun` — привязка TUN к таблице маршрутов -3. `nat_transport_init` — NAT (если включён в конфиге) -4. `init_connections` — ETCP-сокеты, клиентские подключения, handshake -5. `control_server_init` — сервер мониторинга (etcpmon) -6. `ntp_time_init` + `ntp_node_time_init` — синхронизация времени - -### Основной цикл -```c -inst->running = 1; -while (inst->running) { - uasync_poll(inst->ua, 100); +if (!ua) return -1; +struct UTUN_INSTANCE* inst = utun_instance_create(ua, "utun.cfg"); +if (!inst || utun_core_start(inst) < 0 || chat_service_start(inst) < 0) { + utun_instance_destroy(inst); + uasync_destroy(ua, 0); + return -1; } +while (inst->running) uasync_poll(ua, 100); +utun_instance_destroy(inst); +uasync_destroy(ua, 0); ``` -### Остановка и destroy +Цикл запускает приложение; функции `utun_instance_run` нет. +`utun_instance_stop` только снимает `running` и вызывает wakeup. Это не stop сервисов. +При общем UASYNC решение о продолжении poll принимает его владелец. + +## Конструкторы + +| API | Действие и владение | +|---|---| +| `create(ua, path)` | Проверяет аргументы, запускает config updater, разбирает файл и создаёт ядро. Updater может создать/нормализовать файл, ключи и node_id | +| `create_from_config(ua, config)` | Использует готовую модель без updater. При успехе забирает config; при ошибке caller продолжает владеть им | +| `create_from_str(ua, text)` | Копирует ASCIIZ текст в mkstemp `/tmp/utun_cfg_XXXXXX`, вызывает файловый create и удаляет temp. Требует доступной ФС; текст не сохраняется | + +Все возвращают NULL при ошибке и освобождают частично созданные ресурсы. +При `create_from_config` ключи должны быть заданы корректно; производный node_id +проверяется и может исправляться в модели даже при последующей ошибке. +`log_file` и уровни debug меняют logger всего процесса, включая неудачный create. +Путь лога копируется в постоянный буфер, чтобы пережить освобождение config. + +Create уже выводит идентичность X25519/Ed25519, создаёт индекс сетей и соединений, +пулы, routing, обязательные UDP/TCP listeners, топологию/SQLite, мониторинг и +automatic sockets, firewall, router/multicast, NAT detection и настроенный proxy. +Это I/O с возможными ошибками bind, каталогов, БД и ресурсов; создание ядра +не ограничивается выделением структуры. + +При включённой топологии SQLite открывается один раз: `db_path/chats.db`, без пути — +`:memory:`. Каталог подготавливается рекурсивно. Core/UTUN/chat ещё не запущены: +их флаги и `running` равны нулю. Неуспешный обязательный listen или proxy отклоняет +create. Недоступность ICMP raw socket по правам/поддержке отключает только ICMP exit с WARN. + +## Запуск и остановка + +| API | Ресурсы и результат | +|---|---| +| `utun_core_start` | db_sync, NCD/keepalive, постоянные транспорты `[client]`, control, время, broadcast/radio и STUN. Успех задаёт `core_started=running=1` | +| `utun_service_start` | Требует core и топологию. Создаёт UTUN-группу, DATA binding, опциональные TUN/маршруты/NAT и отдельные групповые запросы к `[client]` | +| `chat_service_start` | Объявлен в chat_core.h. Требует core и SQLite. Поднимает CHAT-синхронизацию, stream/media/DM/member_files/call/radio-call, headless и LAN | +| `utun_instance_init` | Последовательно core, затем UTUN при наличии топологии, затем чат при `chatserver_enabled` | + +Start возвращает 0 при успехе или повторном запуске, -1 при ошибке. Готовность +локальных ресурсов не гарантирует UP транспорта или READY группы. +Ошибка старта UTUN/чата вызывает stop этого сервиса. Core start оставляет уже +подготовленные ресурсы для retry/destroy; NTP и STUN нефатальны. +`init` не откатывает ранее запущенные сервисы при отказе последующего. + +UTUN stop снимает DATA binding, TUN/NAT/маршруты, свои групповые запросы и UTUN-группу. +Постоянные `[client]`-транспорты ядра и чат сохраняются. +Chat stop прекращает LAN/headless/stream и фоновые задачи, дожидается workers, +затем закрывает медиа/DM/синхронизацию/CHAT-группы и chat core. SQLite остаётся; +следующий start загружает сохранённые каналы. Stop обоих сервисов идемпотентен. + +Три headless bind-поля принимают IPv4 literal и десятичный порт 0..65535. +Пустая строка отключает listener; 0 выбирает порт ОС, доступный через getter. +Плохой bind или ошибка listener логируется и отключает только его; чат остаётся запущенным. +Call/radio stop отменяет таймеры, снимает клиентов с UASYNC, освобождает кодеки и контекст. +После 11-байтового wire-заголовка PCM копируется в выровненный int16_t буфер перед Opus. + +Глобальный переключатель `set_tun_init_enabled` по умолчанию выключен; влияет +на последующий UTUN start и proxy test mode при create. Дополнительно нужен +`config.global.tun_enabled`. `set_topo_group_enabled` по умолчанию включён; +влияет на последующие create, включая общую БД. Это общие настройки процесса, +а не операции включения/выключения ресурсов существующего instance. + +## Reload + +Всегда сохранять возвращённый указатель: + ```c -utun_instance_stop(inst); // running=0, wakeup -utun_instance_destroy(inst); // полный cleanup в обратном порядке +inst = utun_instance_reload(inst, ua, "utun.cfg"); +if (!inst) { /* прежнее ядро уже уничтожено; обработать ошибку */ } ``` -`destroy` гарантирует порядок: NTP→control→ETCP sockets→db_sync→connections→pings→TUN→tcp_proxy→routing→NAT→etcp_router→conn_mgr→BGP→fw→stcp→networks→config→pools. **uasync не уничтожается** — вызывающий код должен сам вызвать `uasync_destroy()`. -### Reload (SIGHUP) -```c -inst = utun_instance_reload(inst, ua, "utun.conf"); -``` -Сравнивает старый и новый конфиг: -- Если изменились ключи/node_id/tun_ifname → **полный перезапуск** (destroy + create + init) -- Иначе — **селективное обновление**: только изменённые/добавленные/удалённые сокеты, клиенты, линки. Неизменённые не трогаются (без close, без сброса таймеров). +Нужны существующий instance, его же UASYNC и непустой путь. Сначала переоткрывается +лог, updater проверяет файл, затем готовятся новая модель и firewall. +Ошибка подготовки возвращает прежний instance; updater и logger могли уже изменить +файл/глобальные настройки, поэтому общей транзакции с ними нет. -## 3. API +Полный restart выбирается при изменении негорячих global-настроек, списков +my_subnet/route_subnet, а также server-конфига в automatic режиме. +Сюда входят идентичность, имя, db_path, TUN/NAT/proxy, control, keepalive и режим сокетов. +Горячими global-полями считаются firewall, уровни debug и STUN. +Списки сравниваются по содержимому; различия порядка и padding модели могут +консервативно выбрать restart. -### Структура `UTUN_INSTANCE` — ключевые поля +Полный путь уничтожает старое ядро, создаёт новое из уже разобранной модели +и восстанавливает прежние `core_started/utun_started/chat_started/running`. +Изменение config-флага само по себе не включает ранее остановленный сервис. +Ошибки создания/восстановления после destroy возвращают NULL. In-memory SQLite +теряет данные, дисковая БД сохраняется. -| Поле | Тип | Назначение | -|------|-----|------------| -| `name` | `char[16]` | Имя инстанса из конфига | -| `config` | `struct utun_config*` | Полный разобранный конфиг | -| `node_id` | `uint64_t` | Идентификатор узла (из конфига или derived из privkey) | -| `my_keys` | `struct SC_MYKEYS` | Пара ключей X25519 (pub/priv) | -| `my_ed25519_pubkey` / `_privkey` | `uint8_t[32]` | Ed25519 ключи (для подписи) | -| `ua` | `struct UASYNC*` | Главный event loop (один на поток) | -| `running` | `int` | Флаг работы главного цикла | -| `tun` | `struct tun_if*` | TUN-интерфейс | -| `route_subnets` | `struct CFG_ROUTE_ENTRY*` | Маршруты для добавления/удаления системных роутов | -| `rt` | `struct ROUTE_TABLE*` | Таблица маршрутов | -| `topo_groups` | `struct TOPO_GROUPS*` | BGP-подобный обмен маршрутами | -| `connections` | `struct ll_queue*` | Очередь всех ETCP-соединений (индекс по peer_node_id) | -| `etcp_sockets` | `struct ETCP_SOCKET*` | Связный список UDP-сокетов | -| `stcp_server` | `struct stcp_server*` | STCP TCP-сервер | -| `data_pool` | `struct memory_pool*` | Пул для данных пакетов (payload) | -| `pkt_pool` | `struct memory_pool*` | Пул для `struct ETCP_DGRAM` | -| `ack_pool` | `struct memory_pool*` | Пул для `struct ACK_PACKET` | -| `control_srv` | `struct control_server*` | Сервер мониторинга (etcpmon backend) | -| `fw` | `struct firewall_ctx` | Контекст файрвола | -| `nat` / `nat_tr` | `struct eim_nat_ctx` / `nat_transport_ctx` | EIM NAT + транспорт | -| `tcp_proxy_client` | `struct tcp_proxy_client*` | TCP-прокси клиент (опционально) | -| `tcp_proxy_server` | `struct tcp_proxy_server` | TCP-прокси сервер (exit node) | -| `router_bindings` / `router_conns` | | Привязки и соединения ETCP-роутера | -| `conn_mgr` | `struct CONN_MGR*` | Менеджер соединений (может быть NULL) | -| `db_sync` | `struct DB_SYNC*` | Распределённая синхронизация БД (может быть NULL) | -| `networks` | `struct ll_queue*` | Очередь сетей (NETWORK_ENTRY, индекс по 56-bit id) | -| `ntp` / `ntp_node` | `struct NTP_TIME` / `NTP_NODE_TIME` | Синхронизация времени | -| `next_socket_id` | `uint8_t` | Счётчик уникальных ID сокетов (0–255) | -| `socket_init_status` | `int` | Статус инициализации сокетов: 0=OK, 1=частично, -1=ошибка | -| `routed_packets` / `dropped_packets` | `uint64_t` | Счётчики маршрутизированных/отброшенных пакетов | +Частичный путь заранее готовит отдельный индекс сетей. В ручном режиме endpoints +сопоставляются по имени `[server:name]` и всем параметрам: адрес/порт/IPv6 scope, +UDP/TCP, интерфейс, mark/FIB, MTU, type, REALITY/SOCKS, IPv6 mode и only_local. +Неизменённый endpoint сохраняет socket ID, generation и выбранный ОС порт при port=0. +Удаление TCP закрывает listener и TCP-модель, UDP использует собственный teardown. +Probe и endpoints других владельцев не удаляются только из-за отсутствия в server-конфиге. -### Основные функции +Новые endpoints создаёт общая UDP/TCP factory. При изменении servers/clients +перестраиваются постоянные `[client]`; неизменённый список сохраняет handles/таймеры. +Копии клиентских параметров не ссылаются на прежний config. +Далее заменяются config, networks и firewall, перепубликуются изменившиеся адреса +и перезапускается STUN при работающем core. -| Функция | Описание | -|---------|----------| -| `utun_instance_create(ua, config_file)` | Загрузка конфига, аллокация инстанса, вызов `instance_init_common()`. Возвращает готовый к `init` инстанс или NULL | -| `utun_instance_create_from_config(ua, config)` | Создание из уже разобранного конфига (владение передаётся инстансу) | -| `utun_instance_create_from_str(ua, config_text)` | Создание из строки конфига (временный файл → parse → delete). Для тестов | -| `utun_instance_init(instance)` | **Основной оркестратор инициализации**. Запускает все подсистемы в правильном порядке, устанавливает `running=1` | -| `utun_instance_destroy(instance)` | **Полный cleanup** всех ресурсов в обратном порядке зависимостей | -| `utun_instance_stop(instance)` | Установка `running=0` + `uasync_wakeup()` для выхода из mainloop | -| `utun_instance_reload(instance, ua, config_file)` | Перезагрузка конфига по SIGHUP: полная при изменении ключей/TUN, иначе селективная | -| `utun_instance_set_tun_init_enabled(enabled)` | Глобальный флаг: включать инициализацию TUN или нет (для тестов без TUN) | -| `utun_instance_diagnose_leaks(instance, phase)` | Диагностика: подсчёт ETCP-сокетов/соединений/линков, проверка утечек пулов и TUN | +Это не атомарная замена sockets: отказ нового endpoint сохраняет новый config +и отсутствие endpoint для следующей попытки reload. Ошибка подготовки клиентов +сохраняет их прежние самостоятельные handles. Ошибки выводятся в лог; +`socket_init_status=-1` означает отказ хотя бы одного endpoint, 0 — успех. -### Внутренние (static) +## Подписки активности и сна пира -| Функция | Описание | -|---------|----------| -| `instance_init_common(instance, ua, config)` | Общая инициализация для всех create-функций: ключи, node_id, networks, memory pools, routing, TUN, сокеты, BGP, conn_mgr, firewall, etcp_router, routing_bind, tcp_proxy_server/client | -| `local_sockaddr_equal(a, b)` | Сравнение двух sockaddr_storage (IPv4/IPv6) — используется в reload | +Activity и peer-sleep подписки принадлежат instance; fn/arg заимствованы. +Пара fn+arg уникальна; повторный add ничего не меняет. Новые записи идут в голову, +события доставляются синхронно в обратном порядке добавления. +NULL instance/fn и remove отсутствующей записи — no-op; OOM add логируется. + +В callback разрешено удалить себя/соседа и добавить подписку. +Удалённые записи немедленно пропускаются, память удерживается до конца внешней +рассылки обоих видов событий. Новая подписка не участвует в текущем обходе, +но может получить вложенное событие. Вложенный peer-sleep исполняется до продолжения +внешнего; новая эффективная activity прекращает остаток старой activity-рассылки. +Reload/destroy внутри таких callbacks запрещён. + +`utun_set_client_activity` нормализует requested mode, учитывает stream hold, +применяет Android standby при соответствующей сборке и затем рассылает изменение. +Повтор согласует режим без повторного события; ошибка standby откатывает состояние. +SERVER возвращает 0 без изменения; phone_state этим API не меняется. + +## Уничтожение и диагностика + +Destroy снимает running/core_started и запрещает повторный вход, останавливает +STUN/NAT detection/padding, NTP/control/proxy, чат/UTUN и core-client handles, +затем мониторинг/automatic sockets и топологию/broadcast/radio/multicast. +Далее закрывает endpoints с учётом UDP/TCP, db_sync, STCP/REALITY, ETCP и pending pings, +routing/media/router/firewall, сети, config, пулы и оставшиеся подписки. +Списки сокетов и соединений обходятся заново после закрывающих callbacks. +Deferred callbacks выкачиваются, пока UASYNC жив. + +Общая SQLite закрывается последней. Caller обязан завершить заимствованные +statements/backups до destroy. BUSY логируется, close_v2 откладывает физическое +освобождение БД до закрытия оставшихся SQL-объектов; instance после возврата уже недействителен. +Ни logger процесса, ни UASYNC destroy ядра не уничтожает. + +`utun_instance_diagnose_leaks` печатает sys/info счётчики и простые sanity-проверки +списков. Надпись POTENTIAL LEAK о живом ресурсе до cleanup не доказывает утечку; +проверка произвольных повреждённых указателей не гарантируется. +`instance_find_conn` ищет node_id в основном ETCP и старом TCP-реестре, возвращает +заимствованный указатель либо NULL, включая NULL instance; готовность UP/READY не обещает. + +Основные поля: `name[MAX_CONN_NAME_LEN]` (64), собственный config, node_id/ключи, +заимствованный ua, три *_started и running, topo_groups/SQLite, conn_mgr (у UTUN-группы), +etcp_sockets (UDP/TCP), stcp_servers (список), config_conn_handles, pools и networks. +Системные маршруты принадлежат отдельному `tun_routes`, а не списку config. +Подробные поля/типы описаны в заголовке. + +Проверки и платформенные ограничения фиксируются в +[utun_instance_audit_findings.txt](../doc/utun_instance_audit_findings.txt).