You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

400 lines
20 KiB

/**
* @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_<channel_id>). База открывается в topo_groups_init()
* если в конфиге задан db_path.
*/
#ifndef TOPO_GROUP_H
#define TOPO_GROUP_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#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