/** * @file topo_group.h * @brief BGP-подобный обмен топологией узлов между пирами через ETCP. * * Идея: модуль автоматически выстраивает карту маршрутизации между узлами, * используя только пассивное наблюдение (подписка на события) * Можно иметь много групп узлов. Группа - это список узлов. * Для каждой группы выстраивается своя независимая таблица маршрутизации. * Один узел может входить в любое число групп. * Механика: * - Узлы обмениваются информацией друг о друге (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_). База открывается в topo_groups_init() * если в конфиге задан db_path. */ #ifndef TOPO_GROUP_H #define TOPO_GROUP_H #ifdef __cplusplus extern "C" { #endif #include #include #include "../lib/ll_queue.h" #include "../lib/memory_pool.h" #include "../lib/sqlite3.h" #include "route_lib.h" #include "topo_node.h" #include "secure_channel.h" struct UTUN_INSTANCE; struct NAT_DETECTION; struct CONN_MGR; struct TOPO_RECOVERY_CTX; struct TOPO_GROUP_CONNECT; struct TOPO_PEER_REQUEST; struct broadcast_ctx; struct radio_ctx; /** Callback when a node's info (pubkeys, addresses) is persisted in the DB */ typedef void (*topo_node_updated_fn)(struct UTUN_INSTANCE* inst, uint64_t node_id, const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey); /* ── BGP node event callbacks (appearance/update/removal) ── */ #define TOPO_NODE_EVENT_NEW 0 #define TOPO_NODE_EVENT_UPDATE 1 #define TOPO_NODE_EVENT_REMOVE 2 typedef void (*topo_node_event_fn)(struct TOPO_GROUP* group, uint64_t node_id, int event, void* arg); struct topo_node_cbk_entry { topo_node_event_fn fn; void* arg; struct topo_node_cbk_entry* next; }; /* ── Global nodeinfo callbacks (one per instance, any node change) ── */ typedef void (*nodeinfo_cbk_fn)(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, void* arg); struct nodeinfo_cbk_entry { nodeinfo_cbk_fn fn; void* arg; struct nodeinfo_cbk_entry* next; }; void utun_add_nodeinfo_cbk(struct UTUN_INSTANCE* instance, nodeinfo_cbk_fn fn, void* arg); void utun_remove_nodeinfo_cbk(struct UTUN_INSTANCE* instance, nodeinfo_cbk_fn fn, void* arg); void topo_fire_nodeinfo_cbk(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node); // ETCP ID для пакетов топологии: ETCP_ID_TOPO_ENTRY (см. etcp_api.h) /* Групповая сессия принадлежит паре (group, peer), транспорт разделяется через NCD. * JOIN_GROUP не добавляет мембера: CHAT проверяет локальную таблицу участников * (chat_join.h / member_sync). Поздний commit мембера повторяет JOIN на готовом NCD. * * Каждая сторона создаёт случайный ненулевой local_epoch. JOIN несёт src_epoch, * dst_epoch=0 и тип группы. Получатель проверяет группу/членство/тип, посылает свой * JOIN и JOIN_ACCEPT(src=local_epoch, dst=полученный src). JOIN_REJECT возвращает * dst исходного запроса и причину; чужой/старый ответ не меняет текущую сессию. * Одновременные JOIN допустимы; повтор того же JOIN не сбрасывает состояние. * LEAVE до получения поколения пира имеет dst=0 и отменяет только предложение * с совпадающим src у ещё не согласованного получателя. После ACCEPT нужны оба id. * После ACCEPT стороны запрашивают таблицы. Все остальные сообщения содержат * оба поколения и принимаются только в согласованной сессии. TABLE_COMPLETE * отправляется после передачи транспорту всей таблицы отправителя. * READY = живой транспорт + ACCEPT + table_sent + table_received. * * Новое присоединение и REINIT создают новое поколение; REINIT сохраняет пути. * Поколения защищают сообщения, а доступность существующего пути определяется * его соединением. Recovery дополнительно ожидает READY группового пира. * JOIN_READY чата означает добавление мембера и не заменяет этот обмен. * Формат протокола изменяется без обратной совместимости. */ // Sub-команды #define TOPO_SUBCMD_NODEINFO 0x04 // полная информация об узле + подсети #define TOPO_SUBCMD_REQUEST_TABLE 0x05 // запрос полной таблицы #define TOPO_SUBCMD_WITHDRAW 0x06 // узел стал недоступен #define TOPO_SUBCMD_TABLE_COMPLETE 0x0B // завершение начальной синхронизации таблицы #define TOPO_SUBCMD_ERR_GROUP_MISMATCH 0x0C // ошибка несоответствия типа группы #define TOPO_SUBCMD_JOIN_GROUP 0x0D // согласование участия в группе #define TOPO_SUBCMD_RESYNC 0x0E // общий запрос: «я переподключился, заново анонсируй свои группы» #define TOPO_SUBCMD_LEAVE_GROUP 0x0F // завершить участие, сохранив общий транспорт #define TOPO_SUBCMD_JOIN_ACCEPT 0x10 #define TOPO_SUBCMD_JOIN_REJECT 0x11 enum topo_join_reject { TOPO_JOIN_NO_GROUP = 1, TOPO_JOIN_NOT_MEMBER, TOPO_JOIN_WRONG_TYPE, TOPO_JOIN_UNAVAILABLE }; #define MAX_HOPS 16 #define BGP_NODES_HASH_SIZE 256 /* Заголовок всех групповых сообщений. RESYNC — единственное исключение. */ struct TOPOMSG_HEADER { uint8_t cmd, subcmd; uint64_t group_id; uint64_t src_epoch, dst_epoch; } __attribute__((packed)); struct TOPOMSG_NODEINFO_PKT { struct TOPOMSG_HEADER h; struct TOPOMSG_NODE node; } __attribute__((packed)); struct TOPOMSG_WITHDRAW_PKT { struct TOPOMSG_HEADER h; uint64_t node_id, wd_source; } __attribute__((packed)); struct TOPOMSG_JOIN_GROUP { struct TOPOMSG_HEADER h; uint8_t group_type; /* JOIN/ACCEPT: тип группы; REJECT: topo_join_reject */ } __attribute__((packed)); struct TOPOMSG_RESYNC { uint8_t cmd, subcmd; } __attribute__((packed)); struct TOPOMSG_ERR_GROUP_MISMATCH { struct TOPOMSG_HEADER h; uint8_t expected_type, received_flags; } __attribute__((packed)); struct TOPO_GROUP_CONN_ITEM { struct TOPO_GROUP* group; uint64_t node_id; struct ETCP_CONN* conn; // NULL до UP, в состоянии CONNECTING struct NODE_CONN_DIRECT* handle; // владение BGP этим conn (NCD-handle) 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 table_received; // TABLE_COMPLETE текущей сессии принят uint8_t table_sent; // ответ на запрос пира целиком передан транспорту }; struct ETCP_LINK; #define TOPO_NODE_REGISTRY_HASH_SIZE 256 // node_registry entries: queue_entry_new(8 + sizeof(void*)) // data[0..7] = node_id (hash key, index_size=8) // data[8..15] = struct TOPO_NODE* pointer /** * @brief Группа топологии (одна на каждый group_id: utun, чат-группа, …) */ struct TOPO_GROUP { struct ll_entry ll; uint64_t group_id; // уникальный идентификатор группы uint8_t group_type; // TOPO_GROUP_TYPE_* struct UTUN_INSTANCE* instance; 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]; char channel_id[64]; // channel_id для групп типа CHAT struct CONN_MGR* conn_mgr; // менеджер соединений для этой группы struct TOPO_RECOVERY_CTX* recovery; // один последовательный recovery на группу struct TOPO_GROUP_CONNECT* connect; // авто-подключение к узлам группы (CHAT) struct broadcast_ctx* broadcast; // broadcast protocol per-group context 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; }; /** * @brief Контейнер всех групп топологии экземпляра */ struct TOPO_GROUPS { struct UTUN_INSTANCE* instance; struct ll_queue* group_list; // ll_queue of TOPO_GROUP entries struct ll_queue* node_registry; // глобальный реестр TOPO_NODE* по node_id (hash=8) struct memory_pool* v4_sock_meta_pool; struct memory_pool* v4_addr_pool; struct memory_pool* v6_sock_meta_pool; struct memory_pool* v6_addr_pool; struct memory_pool* v4_subnet_pool; struct memory_pool* v6_subnet_pool; topo_node_updated_fn node_updated_cb; /* optional — set by chatgui's member_sync */ }; /** * @brief Создаёт контейнер групп и группу utun по умолчанию (group_id=1). * * @param instance экземпляр utun с node_id и конфигом * @return TOPO_GROUPS или NULL при ошибке */ struct TOPO_GROUPS* topo_groups_init(struct UTUN_INSTANCE* instance); /** * @brief Обработчик события смены active mode (подписчик utun_add_activity_cbk). * * Обновляет свой nodeinfo и рассылает его по всем группам и активным BGP-пирам. */ void topo_group_on_activity_change(struct UTUN_INSTANCE* instance, int active, void* arg); /** * @brief Освобождает все группы и контейнер. * * @param instance экземпляр utun */ void topo_groups_destroy(struct UTUN_INSTANCE* instance); /** * @brief Возвращает группу по умолчанию (group_id=TOPO_GROUP_UTUN=0x8000000000000000). */ struct TOPO_GROUP* topo_groups_get_default(struct TOPO_GROUPS* g); /** * @brief Находит группу по id. */ struct TOPO_GROUP* topo_groups_find(struct TOPO_GROUPS* g, uint64_t group_id); /** * @brief Создаёт новую группу с указанным типом. * * @param g контейнер групп * @param group_id уникальный идентификатор * @param group_type TOPO_GROUP_TYPE_UTUN или TOPO_GROUP_TYPE_CHAT * @param channel_id идентификатор канала для групп типа CHAT (может быть NULL) * @return TOPO_GROUP или NULL при ошибке */ struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t group_id, uint8_t group_type, const char* channel_id); /** * @brief Удаляет одну группу по id (разрушает conn_mgr, connect-цикл, broadcast, * recovery, узлы и подписчиков node_cbks) и извлекает её из group_list. * * Используется при локальном удалении чат-канала. Группу по умолчанию * (TOPO_GROUP_UTUN) удалять нельзя — функция это игнорирует. */ void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id); /** * @brief Добавляет conn в senders_list (если нет), отправляет запрос таблицы (nodeinfo). * * Вызывается при ETCP on_up. */ int topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn); /* Готовность относится только к паре (group, peer): собственный снимок отправлен, * снимок пира принят до TABLE_COMPLETE текущего REQUEST_TABLE. Транспортный UP * этого не гарантирует. Повторное new_conn не сбрасывает готовность; REINIT * начинает новый обмен с новым поколением. Старый TABLE_COMPLETE игнорируется. * Это состояние обмена таблицами, а не добавление мембера (см. chat_join.h). */ int topo_group_peer_ready(const struct TOPO_GROUP* group, uint64_t peer_id); enum topo_peer_phase { TOPO_PEER_FAILED, TOPO_PEER_CONNECTING, TOPO_PEER_SYNCING, TOPO_PEER_READY }; /* Запрос присоединения делит попытку с другими запросами этой пары (группа, пир). * Единственный NCD handle принадлежит группе с начала CONNECTING. REQUEST не * владеет транспортом. Проверка CHAT-членства общая с входящим JOIN_GROUP. * close отменяет только свой запрос; последний запрос отменяет незавершённую * попытку, если её не удерживает другой инициатор. READY сохраняется в группе. * Запрос остаётся валиден после удаления группы (phase=FAILED), caller обязан * закрыть его. Все операции выполняются в потоке единственного uasync. * progress — timebase последнего принятого NODEINFO/шага обмена, не polling time. */ int topo_group_peer_open(struct TOPO_GROUP* group, uint64_t node_id, struct TOPO_PEER_REQUEST** out); void topo_group_peer_close(struct TOPO_PEER_REQUEST* request); enum topo_peer_phase topo_group_peer_phase(const struct TOPO_PEER_REQUEST* request); uint64_t topo_group_peer_progress(const struct TOPO_PEER_REQUEST* request); /** * @brief Удаляет conn из senders_list, очищает paths во всех nodes, отправляет withdraw если node unreachable. * * Вызывается при ETCP on_down. */ enum topo_group_remove_reason { TOPO_REMOVE_TRANSPORT_DOWN, TOPO_REMOVE_LOCAL_LEAVE, TOPO_REMOVE_REMOTE_LEAVE, TOPO_REMOVE_MEMBER_INVALID, TOPO_REMOVE_REJECTED }; /* Только потеря транспорта запускает recovery каскадно потерянных маршрутов. */ void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, enum topo_group_remove_reason reason); /** * @brief Обрабатывает пакет NODEINFO. * * Проверка версии, обновление или создание TOPO_NODEQ, добавление пути, * вставка в роутинг, 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); int topo_group_join_peer(struct TOPO_GROUP* group, struct NODE_CONN_DIRECT* handle); int topo_group_leave_peer(struct TOPO_GROUP* group, struct NODE_CONN_DIRECT* handle); /** * @brief Обрабатывает WITHDRAW. * * Удаляет node из роутинга и nodes, broadcast withdraw. * * @return 0 при успехе */ int topo_group_process_withdraw(struct TOPO_GROUP* group, struct ETCP_CONN* sender, const uint8_t* data, size_t len); /** * @brief Отправляет NODEINFO пакет одному conn (всегда local_node). */ int topo_group_send_nodeinfo(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, struct ETCP_CONN* conn, uint16_t cumulative_rtt); /** * @brief Отправляет WITHDRAW для node_id (вызывает broadcast_withdraw). */ void topo_group_send_withdraw(struct TOPO_GROUP* group, uint64_t node_id); /** * @brief Поиск оптимального ETCP соединения для указанного node_id. * * Перебирает paths узла, выбирает путь с минимальным hop_count. * * @param group указатель на TOPO_GROUP * @param node_id целевой узел * @return оптимальный ETCP_CONN* или NULL */ struct ETCP_CONN* topo_group_find_conn_for_node(struct TOPO_GROUP* group, uint64_t node_id); /** * @brief Добавляет путь (conn) в paths узла. * * @return 0 при успехе */ int topo_group_add_path(struct TOPO_GROUP_NODE* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count, uint16_t cumulative_rtt); /** * @brief Удаляет conn из paths узла. * * @return 1 если путей не осталось (unreachable) */ int topo_group_remove_path(struct TOPO_GROUP_NODE* nq, struct ETCP_CONN* conn); void topo_groups_set_node_updated_cb(struct TOPO_GROUPS* groups, topo_node_updated_fn fn); /** Разрешить/запретить NAT check для локальных подсетей (тестовый хелпер, делегат в nat_detection) */ void topo_group_set_nat_check_local(struct TOPO_GROUP* group, int allow); /* ── Рация (PTT walkie-talkie, per-CHAT-группа) ── */ /* Включить/выключить прослушивание рации канала: выставляет local_node->radio и * рассылает NODEINFO (TOPO_FLAG_RADIO) через BGP. Работает только для CHAT-групп. */ void topo_group_set_radio(struct TOPO_GROUP* group, int on); /* Число узлов группы, слушающих рацию (radio==1), без учёта себя. */ int topo_group_radio_subscribers(struct TOPO_GROUP* group); /* BGP node event callbacks ── */ void topo_group_add_node_cbk(struct TOPO_GROUP* group, topo_node_event_fn fn, void* arg); void topo_group_remove_node_cbk(struct TOPO_GROUP* group, topo_node_event_fn fn, void* arg); #ifdef __cplusplus } #endif #endif // TOPO_GROUP_H