Align instance guide and core lifecycle documentation with audited code

This commit is contained in:
evgeny committed 2026-10-26 09:08:42 +01:00
1 parent 9201d8c4f7
commit 06ad255b95
2 files changed
+198 -95

No files matched your search

+34 -2
View File
@@ -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.
+164 -93
View File
@@ -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).