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
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
|
|
|