docs: clarify core, group and chat header contracts

This commit is contained in:
evgeny committed 2026-09-28 12:01:08 +03:00
1 parent 72fee9e245
commit 01fcefdec5
19 files changed
+396 -414

No files matched your search

+16 -44
View File
@@ -1,3 +1,8 @@
/* conn_mgr — соединение с узлом группы напрямую, в обратную сторону или через посредника.
* Один менеджер принадлежит одной группе; open выдаёт отдельный handle владельцу.
* DIRECT использует NCD, REVERSE просит пира подключиться, INDIRECT выбирает посредника.
* Используется сервисами для доставки данных. Групповые сессии открывают через
* topo_group_peer_open(). Все операции — в потоке uasync; handle закрывает вызывающий. */
#ifndef CONN_MGR_H
#define CONN_MGR_H
@@ -15,44 +20,9 @@ struct UTUN_INSTANCE;
struct ETCP_CONN;
struct TOPO_GROUP;
/**
* @file conn_mgr.h
* @brief Подключение к узлам P2P-сети. Handle-based API.
*
* Подключает к удалегному узлу либо напрямую, либо через посредника с прямыми адресами.
* При открытии выдаёт handle. и держит подключение открытым пока не закроешь handle.
* Отправлять можно обычным send через роутер - он сам разберётся как отправить,
* либо send этого моделя - он точно отправит через этот мост.
*
* Один CONN_MGR = одна TOPO_GROUP. Создаётся conn_mgr_init(group), живёт пока
* существует группа. Внутри: кеш entries (по одной на целевой узел), список
* handle'ов (по одному на каждый вызов open), фоновые ping/idle/мониторинг.
*
* === Единая точка входа: conn_mgr_open ===
*
* 3-фазное подключение (DIRECT→REVERSE→INDIRECT).
* Коллбэк: CONN_EVENT_UP (успех) или CONN_EVENT_TIMEOUT (провал).
*
* === 3 фазы ===
*
* 1. DIRECT: node_conn_direct на все адреса, NAT-фильтрация, LAN broadcast ping.
* 2. REVERSE: у нас прямой IP — шлём DIRECT_REQ цели через BGP.
* 3. INDIRECT: через посредника с минимальной суммой RTT (etcp_router).
*
* === Фоновые процессы ===
*
* bg_ping: раз в ~100ms один узел группы за тик. Поддерживает свежие RTT.
* candidate_ping: раз в ~2с, удаляет протухших кандидатов.
*
* === Refcounting и закрытие ===
*
* Несколько handle на один ETCP_CONN. conn_mgr_close удаляет ОДИН handle.
* Последний handle: DIRECT/REVERSE — DISCONNECT + NCD CLOSE.
*/
enum conn_mgr_event {
CONN_EVENT_UP = 0, /* связь есть. handle жив, можно отправлять данные */
CONN_EVENT_DOWN = 1, /* временный обрыв. handle жив, само восстановится. НЕ закрывать */
CONN_EVENT_DOWN = 1, /* связь потеряна; handle остаётся у владельца, успех восстановления не гарантирован */
CONN_EVENT_TIMEOUT = 2, /* подключение не удалось. handle жив, закройте сами */
};
@@ -69,19 +39,20 @@ typedef void (*conn_mgr_cb_t)(struct CONN_MGR_HANDLE* h,
/* ═══════ lifecycle ═══════ */
/* Создать менеджер группы и его фоновые проверки; NULL при ошибке. */
struct CONN_MGR* conn_mgr_init(struct TOPO_GROUP* group);
/* Остановить проверки и освободить менеджер. Все клиентские handles закрыть ДО destroy. */
void conn_mgr_destroy(struct CONN_MGR* mgr);
/* Приём управляющего пакета от etcp_router; забирает entry. */
void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry);
/* ═══════ единая точка входа ═══════ */
/**
* Единственная публичная функция подключения — 3-фазное (DIRECT→REVERSE→INDIRECT).
* Используется медиа-доставкой. Пиры/группы подключаются через node_conn_direct,
* invite — через topo_group_invite.
*
* Ошибка/таймаут: CONN_EVENT_TIMEOUT.
* node_id и group_id ВСЕГДА валидны в коллбэке (даже при TIMEOUT).
* Возврат 0 — запрос принят, -1 — ошибка запуска. cb обязателен.
* Ошибка до запуска может возвращаться без callback. В callback h может быть NULL.
* Успешно выданный handle закрывает вызывающий, в том числе после TIMEOUT.
*/
int conn_mgr_open(struct UTUN_INSTANCE* inst,
uint64_t group_id,
@@ -89,15 +60,16 @@ int conn_mgr_open(struct UTUN_INSTANCE* inst,
conn_mgr_cb_t cb, void* cb_arg,
struct CONN_MGR_HANDLE** out_handle);
/** Закрыть handle. Последний handle рвёт соединение. */
/** Закрыть свой handle. Последний освобождает ресурсы CM; чужие NCD-владельцы сохраняют транспорт. */
void conn_mgr_close(struct CONN_MGR_HANDLE* h);
/* ═══════ доступ к соединению ═══════ */
/** ETCP_CONN для DIRECT/REVERSE, NULL для INDIRECT. */
/** Заимствованный ETCP_CONN от NCD, если он есть; NULL для INDIRECT или отсоединённого handle. */
struct ETCP_CONN* conn_mgr_get_conn(struct CONN_MGR_HANDLE* h);
/** Отправить данные (для INDIRECT — через посредников). Забирает владение e. */
/** Передать e->dgram через маршрутизатор CM. Освобождает entry при ненулевом h;
* буфер dgram отдельно не освобождает. При h==NULL entry остаётся у вызывающего. */
int conn_mgr_send(struct CONN_MGR_HANDLE* h, struct ll_entry* e);
/* ═══════ мониторинг (вызывается извне) ═══════ */
+22 -11
View File
@@ -2,6 +2,9 @@
// Маршрутизирует сервисные пакеты до целевой ноды, на промежуточных нодах ретранслирует
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, ретрансмиты, inflight-контроль.
// Подпись/шифрование вынесены в автономный модуль route_crypto (encode/decode).
// Логический канал задаётся (group_id, node_id, svc_id), физический транспорт берётся у топологии.
// Сервисы регистрируют приём через bind, отправляют через route_send и соблюдают backpressure.
// Все операции и callbacks — в потоке uasync. Закрытие группы удаляет её каналы и ожидающий транзит.
//
// Формат SVC_ROUTE пакета:
// [cmd:1] [group_id:8] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [timestamp:2] [reset_id:8]
@@ -77,8 +80,8 @@ struct TRANSIT_QUEUE {
uint64_t group_id; // = data[0..7]
uint64_t src_node_id; // = data[8..15]
uint64_t dst_node_id; // = data[16..23]
struct ll_queue* q; // FIFO транзитных пакетов (ll_entry с dgram), начиная с data[16]
struct queue_waiter_handle waiter; // backpressure waiter на bp-очередь (normalizer->input или tx_queue)
struct ll_queue* q; // FIFO ещё не переданных транспорту пакетов
struct queue_waiter_handle waiter; // ожидание свободной conn->send_input_q
struct ETCP_CONN* conn; // next_hop (для etcp_send в drain_cb)
};
@@ -133,9 +136,9 @@ struct ETCP_ROUTER_CONN {
uint64_t reset_id; // собственный идентификатор; не меняется при рестарте пира
uint64_t peer_reset_id; // подтверждённый идентификатор пира (0 = handshake не завершён)
uint64_t pending_peer_id; // кандидат, ещё не имеющий права менять состояние сессии
uint64_t pending_challenge;
uint64_t challenge_sent_tb;
void* handshake_timer;
uint64_t pending_challenge; // случайный запрос подтверждения кандидату
uint64_t challenge_sent_tb; // время отправки запроса, 0.1 ms
void* handshake_timer; // таймер повторов согласования
uint8_t start_sent; // получен первый ACK данных
uint8_t peer_sync_done; // 1 = завершено подтверждение peer ID
@@ -203,41 +206,48 @@ struct ETCP_ROUTER_BINDINGS {
// Инициализация: etcp_bind(ETCP_ID_SVC_ROUTE) + очистка bindings + создание router_conns
int etcp_router_init(struct UTUN_INSTANCE* inst);
// Деинициализация
// Закрыть каналы, снять обработчик и освободить маршрутизатор.
void etcp_router_destroy(struct UTUN_INSTANCE* inst);
// Зарегистрировать обработчик сервиса
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback);
// Снять обработчик; само по себе не закрывает логические каналы сервиса.
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q).
// Принимает владение entry во всех исходах, включая ошибку.
// entry->dgram = svc_id[1] || payload; 0=принято в очередь, <0=ошибка, не подтверждение доставки.
// force разрешает превысить лимит числа пакетов в send_q.
// mode — битовая маска ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (см. route_crypto.h), 0 = обычный пакет.
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id, struct ll_entry* entry, int force, int mode);
// Найти/создать состояние seq-подключения по (group_id, remote_node_id, svc_id)
// Найти/создать логический канал. Указатель заимствован до close/destroy, NULL при ошибке.
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
uint64_t group_id, uint64_t remote_node_id, uint8_t svc_id);
// Есть ли сейчас физический маршрут до узла (прямой, indirect-посредник или глобальный).
// 1 = есть ETCP-соединение для отправки, 0 = нет (передача встанет в no_route).
// 1 = найден транспорт (для self всегда 1); это не гарантия UP/готовности логического канала.
int etcp_router_has_route(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id);
// Отправить данные с авто-seq и контролем inflight
// data: payload без svc_id, mode: ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (0 = обычный)
// Копирует data; 0=принято в очередь, <0=ошибка. Доставка подтверждается отдельно протоколом.
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len, int mode);
// Закрыть seq-подключение
// Закрыть канал и уведомить сервис. После вызова rconn использовать нельзя.
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
// Установить рабочий max_inflight, пересчитывает inflight_limit и state machine
void etcp_router_set_max_inflight(struct ETCP_ROUTER_CONN* rconn, uint16_t new_max);
// Backpressure: зарегистрировать/отменить waiter на send_q очереди
// Дождаться порога send_q. h должен быть обнулён и жить до callback/отмены.
// Callback может выполниться внутри вызова. Повторная регистрация отменяет прежнюю.
// Если канала нет, ожидание не регистрируется. Перед освобождением arg отменить waiter.
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
// Отмена относится к исходной очереди, даже если логический канал уже перезапущен.
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h);
@@ -252,8 +262,9 @@ int etcp_router_send_q_has_room(struct UTUN_INSTANCE* inst, uint64_t group_id, u
// Используется при etcp_conn_reinit чтобы избежать гонки с очисткой ETCP-очередей
void etcp_router_pause_retrans_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
// Закрыть все router_conn для указанного (group_id, remote_node_id) (peer умер)
// Закрыть все каналы и ожидающий транзит группы. Сначала сервисы отменяют свои producers/waiters.
void etcp_router_close_group(struct UTUN_INSTANCE* inst, uint64_t group_id);
// Закрыть логические каналы одного пира в группе.
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id);
// Начать новую локальную сессию для конкретного peer+svc в группе и уведомить сервис.
+40 -52
View File
@@ -1,32 +1,13 @@
/**
* @file topo_group.h
* @brief BGP-подобный обмен топологией узлов между пирами через ETCP.
* @brief Групповые сессии и обмен маршрутами поверх общего транспорта NCD.
*
* Идея: модуль автоматически выстраивает карту маршрутизации между узлами,
* используя только пассивное наблюдение (подписка на события)
* Можно иметь много групп узлов. Группа - это список узлов.
* Для каждой группы выстраивается своя независимая таблица маршрутизации.
* Один узел может входить в любое число групп.
* Механика:
* - Узлы обмениваются информацией друг о друге (pubkey, адреса, подсети)
* через NODEINFO/WITHDRAW сообщения, маршрутизируемые по ETCP.
* - Каждый узел хранит полную таблицу известных узлов (структура TOPO_NODEQ).
* - При подключении нового пира — полная синхронизация таблицы.
* - При отключении пира — withdraw всех узлов, достижимых только через него.
*
* Группы (TOPO_GROUP):
* - TOPO_GROUP_TYPE_UTUN — основная VPN-сеть (обмен маршрутами, подсетями)
* - TOPO_GROUP_TYPE_CHAT — чат-группа (только узлы, без подсетей)
* - Каждая группа изолирована: узлы из utun-группы не видны в чат-группе и наоборот
*
* Дополнительные функции:
* - NAT-детекция: выделена в отдельный модуль nat_detection.h
* - Поиск оптимального маршрута до узла (topo_group_find_conn_for_node)
*
* Хранение: все узлы сохраняются в SQLite (таблицы nodes, node_addresses,
* channels, peers_<channel_id>). База открывается в topo_groups_init()
* если в конфиге задан db_path.
* У каждой группы своя топология; идентичности и адреса узлов общие (topo_node.h).
* UTUN обменивается также подсетями, CHAT допускает только локально известных участников.
* Для подключения используйте peer_open/close: группа владеет NCD с начала попытки.
* JOIN/ACCEPT согласует сессию, NODEINFO/WITHDRAW поддерживают её маршруты.
* Потерю путей обрабатывает topo_recovery, выбор CHAT-пиров — topo_group_connect.
* Все операции — в потоке uasync. Сервисы создают и удаляют свои группы явно.
*/
#ifndef TOPO_GROUP_H
#define TOPO_GROUP_H
@@ -170,19 +151,19 @@ struct TOPOMSG_ERR_GROUP_MISMATCH {
} __attribute__((packed));
struct TOPO_GROUP_CONN_ITEM {
struct TOPO_GROUP* group;
uint64_t node_id;
struct TOPO_GROUP* group; // владелец сессии
uint64_t node_id; // идентичность пира, включая CONNECTING
struct ETCP_CONN* conn; // NULL до UP, в состоянии CONNECTING
struct NODE_CONN_DIRECT* handle; // владение BGP этим conn (NCD-handle)
struct TOPO_PEER_REQUEST* requests;
struct TOPO_PEER_REQUEST* requests; // запросы инициаторов; каждый закрывает свой
uint64_t progress; // время последнего прогресса обмена (timebase)
uint8_t retained; // участие запрошено постоянным владельцем или достигло READY
uint64_t local_epoch, peer_epoch; // поколения сторон текущего присоединения
uint8_t accepted; // JOIN_ACCEPT подтвердил local_epoch
uint8_t tx_failed, transport_down;
struct topo_tx_item *tx_head, *tx_tail;
struct queue_waiter_handle tx_waiter;
void* tx_wake;
uint8_t tx_failed, transport_down; // ошибка отправки / потеря транспорта
struct topo_tx_item *tx_head, *tx_tail; // FIFO команд, ещё не переданных транспорту
struct queue_waiter_handle tx_waiter; // ожидание свободной send_input_q
void* tx_wake; // отложенный запуск отправки
uint8_t table_received; // TABLE_COMPLETE текущей сессии принят
uint8_t table_sent; // ответ на запрос пира целиком передан транспорту
};
@@ -206,7 +187,7 @@ struct TOPO_GROUP {
struct ll_queue* senders_list; // ll_entry.data = TOPO_GROUP_CONN_ITEM; включает CONNECTING с conn=NULL
struct ll_queue* nodes; // TOPO_GROUP_NODE{ll_entry,node_id,paths,...} — per-group узлы, хеш-индекс по node_id(8B)
struct TOPO_GROUP_NODE* local_node; // свой узел в этой группе
uint8_t ed25519_public_key[SC_PUBKEY_SIZE];
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // публичный ключ локального узла
char channel_id[64]; // channel_id для групп типа CHAT
struct CONN_MGR* conn_mgr; // менеджер соединений для этой группы
struct TOPO_RECOVERY_CTX* recovery; // один последовательный recovery на группу
@@ -215,7 +196,7 @@ struct TOPO_GROUP {
struct radio_ctx* radio; // radio (PTT walkie-talkie) per-group context
uint8_t radio_active; // 1 = я слушаю рацию этого канала (TOPO_FLAG_RADIO в NODEINFO)
struct topo_node_cbk_entry* node_cbks; // цепочка подписчиков на события узлов
uint8_t stopping;
uint8_t stopping; // teardown начат; новые попытки запрещены
};
/**
@@ -235,7 +216,8 @@ struct TOPO_GROUPS {
};
/**
* @brief Создаёт контейнер групп и группу utun по умолчанию (group_id=1).
* @brief Создаёт реестр и локальную идентичность; сервисных групп пока нет.
* Открывает общую SQLite: db_path/chats.db, без db_path — :memory:.
*
* @param instance экземпляр utun с node_id и конфигом
* @return TOPO_GROUPS или NULL при ошибке
@@ -257,12 +239,12 @@ void topo_group_on_activity_change(struct UTUN_INSTANCE* instance, int active, v
void topo_groups_destroy(struct UTUN_INSTANCE* instance);
/**
* @brief Возвращает группу по умолчанию (group_id=TOPO_GROUP_UTUN=0x8000000000000000).
* @brief Возвращает UTUN-группу или NULL, если сервис не запущен. Указатель заимствован.
*/
struct TOPO_GROUP* topo_groups_get_default(struct TOPO_GROUPS* g);
/**
* @brief Находит группу по id.
* @brief Заимствованный указатель на группу по id; NULL, если её нет.
*/
struct TOPO_GROUP* topo_groups_find(struct TOPO_GROUPS* g, uint64_t group_id);
@@ -287,9 +269,9 @@ struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t grou
void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id);
/**
* @brief Добавляет conn в senders_list (если нет), отправляет запрос таблицы (nodeinfo).
*
* Вызывается при ETCP on_up.
* @brief Присоединяет существующий транспорт к группе и начинает JOIN/ACCEPT.
* Группа приобретает собственный NCD handle; повтор не сбрасывает сессию. 0/-1.
* Для обычного инициатора используйте отменяемый topo_group_peer_open().
*/
int topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
@@ -311,19 +293,20 @@ enum topo_peer_phase { TOPO_PEER_FAILED, TOPO_PEER_CONNECTING, TOPO_PEER_SYNCING
* CLOSED/leave/удаление группы отсоединяет запрос окончательно.
* Запрос остаётся валиден после удаления группы (phase=FAILED), caller обязан
* закрыть его. Все операции выполняются в потоке единственного uasync.
* progress — timebase последнего принятого NODEINFO/шага обмена, не polling time. */
* progress — timebase последнего принятого NODEINFO/шага обмена, не polling time.
* open: 0 = запрос создан в *out, -1 = ошибка. */
int topo_group_peer_open(struct TOPO_GROUP* group, uint64_t node_id, struct TOPO_PEER_REQUEST** out);
/* Освободить свой запрос; после вызова request недействителен. NULL допустим. */
void topo_group_peer_close(struct TOPO_PEER_REQUEST* request);
struct ETCP_CONN* topo_group_peer_conn(const struct TOPO_PEER_REQUEST* request); /* borrowed, including CONNECTING */
/* Завершить всю сессию этой пары: LEAVE, удаление путей, отсоединение всех запросов. */
void topo_group_peer_leave(struct TOPO_GROUP* group, uint64_t node_id);
/* Текущая фаза; NULL или отсоединённый запрос дают FAILED. */
enum topo_peer_phase topo_group_peer_phase(const struct TOPO_PEER_REQUEST* request);
/* Время последнего прогресса в единицах 0.1 ms; 0, если сессии нет. */
uint64_t topo_group_peer_progress(const struct TOPO_PEER_REQUEST* request);
/**
* @brief Удаляет conn из senders_list, очищает paths во всех nodes, отправляет withdraw если node unreachable.
*
* Вызывается при ETCP on_down.
*/
/* Причина завершения групповой сессии определяет необходимость recovery. */
enum topo_group_remove_reason {
TOPO_REMOVE_TRANSPORT_DOWN,
TOPO_REMOVE_LOCAL_LEAVE,
@@ -331,25 +314,29 @@ enum topo_group_remove_reason {
TOPO_REMOVE_MEMBER_INVALID,
TOPO_REMOVE_REJECTED
};
/* Только потеря транспорта запускает recovery каскадно потерянных маршрутов. */
/* Сбросить сессию и удалить пути через conn, разослать WITHDRAW для потерянных узлов.
* При DOWN живые запросы сохраняют NCD для повторного UP. Только потеря транспорта
* запускает recovery каскадно потерянных маршрутов. */
void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, enum topo_group_remove_reason reason);
/**
* @brief Обрабатывает пакет NODEINFO.
*
* Проверка версии, обновление или создание TOPO_NODEQ, добавление пути,
* Проверка подписи и timestamp, обновление TOPO_GROUP_NODE, добавление пути,
* вставка в роутинг, broadcast если не max hops.
*
* @return 0 при успехе
*/
int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from, const uint8_t* data, size_t len);
/* Присоединить UP-транспорт; переданный handle остаётся у вызывающего. 0/-1. */
int topo_group_join_peer(struct TOPO_GROUP* group, struct NODE_CONN_DIRECT* handle);
/* Отсоединить пира и запланировать LEAVE. Чужой handle не забирает. 0/-1. */
int topo_group_leave_peer(struct TOPO_GROUP* group, struct NODE_CONN_DIRECT* handle);
/**
* @brief Обрабатывает WITHDRAW.
*
* Удаляет node из роутинга и nodes, broadcast withdraw.
* Удаляет затронутые пути; узел удаляется только без оставшихся путей.
*
* @return 0 при успехе
*/
@@ -369,11 +356,12 @@ void topo_group_send_withdraw(struct TOPO_GROUP* group, uint64_t node_id);
/**
* @brief Поиск оптимального ETCP соединения для указанного node_id.
*
* Перебирает paths узла, выбирает путь с минимальным hop_count.
* Выбирает минимум hop_count среди живых путей. Если живых нет, возвращает
* лучший сохранённый путь: ненулевой результат сам по себе не гарантирует UP.
*
* @param group указатель на TOPO_GROUP
* @param node_id целевой узел
* @return оптимальный ETCP_CONN* или NULL
* @return заимствованный ETCP_CONN* или NULL
*/
struct ETCP_CONN* topo_group_find_conn_for_node(struct TOPO_GROUP* group, uint64_t node_id);
+15 -9
View File
@@ -1,11 +1,10 @@
/* CHAT connection policy over cancellable group requests.
* Phase 1: historical connected peers in parallel (up to 128).
* Phase 2: sequential supernode/public peers. Phase 3: local peers.
* Each node is tried once per cycle. CONNECTING: 2s; SYNCING: 5s without progress.
* Goal: one READY supernode or three READY nonmobile peers. After exhaustion,
* retry after 1s (or standby burst). Existing READY group sessions survive
* planner restart/stop. Manual attempts are tracked and cancelled with the group.
* active_count is derived from the current group sessions, not a separate counter.
/* topo_group_connect — выбор пиров и попытки подключения CHAT-группы.
* Сначала ранее подключённые пиры (до 128 параллельно), затем по одному
* суперузлы/публичные узлы, затем локальные. Цель — READY суперузел или три
* READY немобильных пира. CONNECTING: 2 s; SYNCING: 5 s без прогресса.
* После исчерпания кандидатов новый цикл через 1 s или следующий standby burst.
* Планировщик владеет запросами, группа — NCD. Остановка отменяет попытки,
* включая ручные, но сохраняет готовые сессии. Все вызовы — в потоке uasync.
*/
#ifndef TOPO_GROUP_CONNECT_H
#define TOPO_GROUP_CONNECT_H
@@ -15,15 +14,22 @@
struct TOPO_GROUP;
struct ETCP_CONN;
/* Запустить новый автоматический цикл вместо прежнего. 0/-1. */
int topo_group_connect_init(struct TOPO_GROUP* group);
/* Отменить запросы, таймеры и отложенные вызовы планировщика. */
void topo_group_connect_destroy(struct TOPO_GROUP* group);
/* Запланировать проверку изменившихся сессий. */
void topo_group_connect_changed(struct TOPO_GROUP* group);
/* Учесть появление пира; для READY записать connected в БД. */
void topo_group_connect_on_up(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
/* Учесть потерю пира и продолжить подбор кандидатов. */
void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
/* Число текущих READY-пиров группы. */
int topo_group_connect_active_count(struct TOPO_GROUP* group);
/* Перезапустить автоматический цикл, только если нет READY-пиров. */
void topo_group_connect_restart(struct TOPO_GROUP* group);
/* One manual request; shares the group session, no NCD ownership handoff on UP. */
/* Одна ручная попытка, общий запрос к сессии группы. 0=принято, -1=ошибка. */
int topo_group_connect_node_once(struct TOPO_GROUP* group, uint64_t node_id);
#endif
+13 -24
View File
@@ -1,23 +1,11 @@
/**
* @file topo_group_invite.h
* @brief Invite/join к каналу через прямое (ncd) подключение.
* @brief Получение описания канала по прямому NCD-транспорту (INVITE_INFO).
*
* Модуль содержит всю логику работы с invite-ссылками: прямое подключение к
* приглашающему узлу через node_conn_direct (ncd) и проверку членства
* (INVITE_INFO_REQ/RESP) с бутстрапом криптографической идентичности канала.
*
* Поток джойнера (topo_group_invite_join):
* 1. Создаёт/находит группу, сохраняет TOPO_NODE приглашающего.
* 2. Открывает прямое соединение (ncd) и шлёт INVITE_INFO_REQ.
* 3. Получает INVITE_INFO_RESP — проверяет ch_sig, сохраняет канал в БД,
* поднимает инфраструктуру группы (chat_core_ensure_channel_ready).
* 4. Доставляет TGI_EVENT_JOIN.
*
* Инвайтера сторона: tgi_handle_info_req отвечает на INVITE_INFO_REQ —
* проверяет членство в БД и возвращает полную информацию о канале.
*
* conn_mgr при этом используется ТОЛЬКО для медиа — invite больше от него
* не зависит.
* Запрашивает и проверяет подписанные сведения канала, готовит локальную группу.
* Успешный ответ не добавляет участника на других узлах и не означает READY группы.
* Вход по пользовательской ссылке и добавление участника описаны в chat_sync.h/chat_join.h.
* Все операции — в потоке uasync; временные запросы отменяются при остановке чата.
*/
#ifndef TOPO_GROUP_INVITE_H
#define TOPO_GROUP_INVITE_H
@@ -37,8 +25,8 @@ struct NODE_CONN_DIRECT;
#define TGI_SUBCMD_INFO_RESP 0x02
/* события коллбэка джойнера */
#define TGI_EVENT_JOIN 0
#define TGI_EVENT_TIMEOUT 1
#define TGI_EVENT_JOIN 0 /* описание канала принято, локальная инфраструктура готова */
#define TGI_EVENT_TIMEOUT 1 /* попытка завершилась ошибкой или таймаутом */
typedef void (*tgi_cb_t)(uint64_t node_id, uint64_t group_id, int event, void* arg);
@@ -48,17 +36,18 @@ void topo_group_invite_init(struct UTUN_INSTANCE* inst);
/**
* Джойнер: прямое подключение (ncd) к приглашающему + проверка членства.
* ni — TOPO_NODE приглашающего (pubkey + адреса), не владеем.
* При успехе вызывается cb(TGI_EVENT_JOIN), при провале cb(TGI_EVENT_TIMEOUT).
* 0 — попытка начата, -1 — ошибка. До создания попытки ошибка не вызывает cb;
* после создания ошибки дают TGI_EVENT_TIMEOUT, иногда до возврата функции.
* Успешный обмен даёт TGI_EVENT_JOIN; cancel_all завершает ожидание без cb.
*/
int topo_group_invite_join(struct UTUN_INSTANCE* inst, uint64_t group_id,
struct TOPO_NODE* ni, uint64_t node_id,
tgi_cb_t cb, void* cb_arg);
/**
* Инвайтера сторона: прямое подключение (ncd) к целевому узлу для последующей
* отправки CHANNEL_INVITE (через chat_sync, по ETCP_CONN_STATUS_UP).
* Без INVITE_INFO-проверки членства. ni может быть NULL — тогда адреса грузятся
* из БД. Группа владеет NCD; инициатор держит отменяемый групповой запрос.
* Запустить групповую попытку к уже известному участнику, при необходимости
* передав адреса ni (заимствованы; NULL — взять известные). 0/-1.
* Применяется обычная проверка CHAT-членства. Сам CHANNEL_INVITE не отправляет.
*/
int topo_group_invite_to_channel(struct UTUN_INSTANCE* inst, uint64_t group_id,
struct TOPO_NODE* ni, uint64_t node_id);
+43 -29
View File
@@ -2,21 +2,11 @@
* @file topo_node.h
* @brief Модель данных узла сети — структуры, сериализация, wire-формат.
*
* Этот модуль определяет:
* - Как узел представлен в памяти (TOPO_NODE, TOPO_GROUP_NODE, адреса, подсети)
* - Как узел передаётся по сети (packed-структуры TOPOMSG_*)
* - Как узел сериализуется/десериализуется (topo_node_serialize/deserialize)
*
* Хранение:
* - Идентичность узла (pubkeys, адреса, имя) — в глобальном реестре TOPO_GROUPS->node_registry
* Единственный экземпляр TOPO_NODE, разделяется между группами через group_ref_count.
* - Per-group данные (node_id, paths, subnets, connectivity) — в TOPO_GROUP->nodes (очередь TOPO_GROUP_NODE)
*
* Основные понятия:
* TOPO_NODE — глобальная идентичность узла. Встроен в node_registry как queue entry.
* TOPO_GROUP_NODE — узел в контексте группы: node_id + paths, hop_list, связность
* TOPO_NODEPATH — путь до узла через конкретное ETCP-соединение
* TOPOMSG_NODE — wire-формат, передаваемый по NODEINFO
* TOPO_NODE — общие ключи, имя и адреса; реестр хранит указатель с подсчётом ссылок.
* Подписанная запись заменяется целиком при более свежем timestamp, вместе с подписью.
* TOPO_GROUP_NODE — пути, подсети и состояние узла в конкретной группе.
* TOPOMSG_NODE — сетевой формат. Модуль сериализует, подписывает и проверяет записи.
* Работа с реестром и группами выполняется в потоке uasync экземпляра.
*/
#ifndef TOPO_NODE_H
#define TOPO_NODE_H
@@ -87,9 +77,11 @@ struct UTUN_INSTANCE;
#define TOPO_GROUP_TYPE_UTUN 1
#define TOPO_GROUP_TYPE_CHAT 2
/* Результаты проб адресов. *_time — timebase (0.1 ms), *_rtt — те же единицы;
* *_status — PROBE_RESULT_*, probe_status — PROBE_STATUS_*. */
struct TOPO_CONNECTIVITY {
uint8_t probe_status;
uint8_t pending_count;
uint8_t pending_count; // незавершённые пробы
uint64_t probe_start_time;
uint16_t interface_min_rtt;
uint16_t nat_min_rtt;
@@ -103,7 +95,7 @@ struct TOPO_CONNECTIVITY {
uint64_t real_probe_time;
uint64_t ping_req_time;
uint64_t last_ping_time;
void* probe_list;
void* probe_list; // принадлежащие узлу контексты текущих проб
void* probe_deferred_wait; /* контекст отложенной пробы (standby_wait, Android) */
};
@@ -171,17 +163,17 @@ struct TOPO_ADDR_REALITY_OPTS {
struct _topo_head { struct _topo_head* next; };
static inline int topo_list_count(struct _topo_head* head) { int n = 0; while (head) { n++; head = head->next; } return n; }
/** Глобальная идентичность узла. Встроен в TOPO_GROUPS->node_registry как queue entry. */
/** Общая запись узла; node_registry хранит указатель на неё в отдельном ll_entry. */
struct TOPO_NODE {
struct ll_entry ll;
uint32_t group_ref_count;
uint32_t group_ref_count; // ссылки групп, ядра и временных пользователей
uint64_t node_id; uint8_t ver;
uint64_t timestamp; // 0 = неподписанные bootstrap-сведения
uint8_t public_key[SC_PUBKEY_SIZE], ed25519_public_key[SC_PUBKEY_SIZE];
uint8_t x25519_self_sig[64];
uint8_t x25519_self_sig[64]; // Ed25519-подпись общей записи
uint8_t client_type; // CLIENT_TYPE_SERVER/DESKTOP/MOBILE
uint8_t client_activity; // CLIENT_ACTIVITY_STANDBY/ACTIVE
char* node_name;
char* node_name; // собственная строка; освобождается с записью
struct TOPO_SOCKMETA4* v4_sock_meta;
struct TOPO_ADDR4* v4_addrs;
struct TOPO_SOCKMETA6* v6_sock_meta;
@@ -196,9 +188,9 @@ struct TOPO_NODESUBNETS {
struct TOPO_NODEPATH {
struct ll_entry ll;
struct ETCP_CONN* conn;
uint8_t hop_count;
uint16_t cumulative_rtt;
struct ETCP_CONN* conn; // заимствованный next-hop; транспорт удерживает сессия
uint8_t hop_count; // число node_id в массиве сразу после структуры
uint16_t cumulative_rtt; // RTT оставшейся цепочки, без локального линка (0.1 ms)
};
/** Узел в контексте группы (per-group данные). Хранится в TOPO_GROUP->nodes (ll_queue). */
@@ -213,52 +205,74 @@ struct TOPO_GROUP_NODE {
uint64_t conn_mgr_intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; // посредники для INDIRECT
uint8_t conn_mgr_intermediariy_count; // количество посредников
struct TOPO_CONNECTIVITY connectivity; // результаты ping-проб (interface/nat/real)
uint8_t conn_presence; // NODE_CONN_* — какие подключения есть в принципе
uint8_t conn_up; // NODE_CONN_* — какие из них подняты
uint8_t conn_presence; // маска NCONN_*: известные виды подключения
uint8_t conn_up; // маска NCONN_*: работающие виды подключения
uint8_t radio; // 1 = узел слушает рацию канала (TOPO_FLAG_RADIO)
struct NODE_CONN_DIRECT* handle; // активный ncd-handle прямого соединения к узлу
struct NODE_CONN_DIRECT* handle; // не владеет транспортом; NCD принадлежит сессии группы
};
// API — глобальный реестр TOPO_NODE
/* Заимствованный указатель; ref нужен, если запись должна пережить удаление из группы. */
struct TOPO_NODE* topo_node_registry_find(struct TOPO_GROUPS* groups, uint64_t node_id);
/* Принять ni и вернуть запись с одной новой ссылкой. Может вернуть существующий объект.
* При NULL входной ni остаётся у вызывающего. Подписанные записи сравниваются по timestamp. */
struct TOPO_NODE* topo_node_registry_store(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni);
/* Добавить/снять ссылку; последняя unref удаляет запись из реестра и освобождает её. */
void topo_node_registry_ref(struct TOPO_GROUPS* groups, uint64_t node_id);
void topo_node_registry_unref(struct TOPO_GROUPS* groups, uint64_t node_id);
/* Освободить отдельную запись и её списки; зарегистрированную запись отпускать через unref. */
void topo_node_destroy(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni);
// API — per-group
/* Заимствованный узел группы, NULL если не найден. */
struct TOPO_GROUP_NODE* topo_node_find_by_id(struct TOPO_GROUP* group, uint64_t node_id);
/* Отпустить ссылку реестра и подсети; nq и paths не освобождает. */
void topo_nodeq_free_group_fields(struct TOPO_GROUPS* groups, struct TOPO_GROUP_NODE* nq);
/* Полное удаление узла из группы: отменяет влётные пробы, освобождает paths/subnets,
* убирает из group->nodes и освобождает сам nq. Единственный корректный способ удаления узла. */
void topo_nodeq_remove_node(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq);
/* Размер переменной части NODEINFO по счётчикам заголовка. */
int topo_node_dyn_size(const struct TOPOMSG_NODE* msg);
/* Собрать NODEINFO в out; длина или -1. Входные структуры остаются у вызывающего. */
int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
uint64_t group_id, uint8_t flags,
uint8_t* out, size_t out_max, uint16_t cumulative_rtt);
/* Разобрать NODEINFO; 0/-1. Выходные объекты выделены отдельно и передаются вызывающему. */
int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t len, struct TOPO_NODE** out_ni, struct TOPO_NODESUBNETS** out_subnets,
uint64_t** out_hop_list, uint8_t* out_hop_count,
uint16_t* out_cumulative_rtt);
/* Заимствованный список hops: кратчайший живой путь, иначе первый сохранённый.
* out_count обязателен; NULL означает отсутствие пути. */
uint64_t* topo_node_best_hop_list(struct TOPO_GROUP_NODE* nq, uint8_t* out_count, uint16_t* out_rtt);
/* Обновить локальный узел группы и его анонс. 0/-1. */
int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group);
/* Создать подписанную локальную идентичность с отдельной ссылкой ядра. 0/-1. */
int topo_node_init_self(struct UTUN_INSTANCE* instance);
int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance); /* 1=changed, 0=unchanged, -1=error */
/* Обработчик изменений локального сокета: обновляет адреса узла. */
void topo_node_on_socket_changed(struct ETCP_SOCKET* sock, int event, void* arg);
/* Вывести диагностику узлов группы в лог / текстовый буфер. */
void topo_node_dump_all(struct TOPO_GROUP* group);
int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size);
/* 1, если хотя бы в одной группе пора повторить ping этого узла; иначе 0. */
int topo_node_ping_request_cbk(struct TOPO_GROUPS* groups, uint64_t node_id);
/* Обновить время ping и минимальные RTT во всех группах и БД; rtt в единицах 0.1 ms. */
void topo_node_ping_update_rtt(struct TOPO_GROUPS* groups, uint64_t node_id, uint16_t rtt);
/* Минимальный суммарный RTT живого пути в единицах 0.1 ms; UINT16_MAX, если неизвестен. */
uint16_t topo_get_chain_rtt(struct TOPO_GROUP_NODE* nq);
/** Build canonical message for Ed25519 signature: x25519_pubkey || name || client_type || client_activity || addresses */
/* Лимиты буфера подписываемых данных и полного сетевого NODEINFO. */
#define TOPO_SIG_MSG_MAX_SIZE 2048
#define TOPO_NODE_WIRE_MAX_SIZE 16384
/* Канонические байты подписи: node_id, ver, timestamp, X25519-ключ, имя,
* тип/активность и полные списки сокетов/адресов/Reality. Длина или -1. */
int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size);
/** Sign self NODEINFO with Ed25519: build_sig_msg + sc_ed25519_sign → ni->x25519_self_sig */
/* Увеличить timestamp, подписать локальный NODEINFO и сохранить снимок в SQLite. 0/-1. */
int topo_node_sign_self(struct UTUN_INSTANCE* instance, struct TOPO_NODE* ni);
/* Проверить timestamp, привязку node_id к ключу и подпись. 0/-1. */
int topo_node_verify(const struct TOPO_NODE* ni);
static inline const struct TOPO_SOCKMETA4* topo_v4_sock_meta(const struct TOPO_NODE* ni) { return ni->v4_sock_meta; }
+7
View File
@@ -1,3 +1,6 @@
/* topo_recovery — восстановление путей группы после потери транспорта.
* Собирает потерянные узлы, пробует кандидатов через групповые запросы и ждёт
* нужных маршрутов через READY-пиров. Контекст принадлежит группе; работа — в uasync. */
#ifndef TOPO_RECOVERY_H
#define TOPO_RECOVERY_H
@@ -30,9 +33,13 @@ struct TOPO_RECOVERY_CTX;
* NODEINFO/смена состояния лишь планируют проверку через call_soon: текущий
* BGP callback завершается до изменения попытки или освобождения контекста.
* Остановка группы отменяет recovery до освобождения сессий и таблицы узлов. */
/* Добавить потерянную цель до освобождения её записи; rtt — в единицах 0.1 ms. */
void topo_recovery_add_node(struct TOPO_GROUP* group, uint64_t node_id, uint64_t next_hop, uint16_t rtt);
/* Запустить собранные попытки после удаления нерабочих путей. */
void topo_recovery_start(struct TOPO_GROUP* group);
/* Отложить проверку прогресса; повторные уведомления объединяются. */
void topo_recovery_changed(struct TOPO_GROUP* group);
/* Отменить попытку и освободить контекст; готовые сессии остаются у группы. */
void topo_recovery_cancel_all(struct TOPO_GROUP* group);
#ifdef __cplusplus