From 59d307f98b8dcb83833e032dc0b4e889f5a75d10 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 5 Jul 2026 19:08:13 +0300 Subject: [PATCH] conn_mgr.h: document in ll_queue style; config_parser/utun_instance updates --- src/config_parser.c | 64 +++++- src/config_parser.h | 9 + src/conn_mgr.h | 491 +++++++++++++++++++++++++++++++++----------- src/utun_instance.c | 69 +++++++ src/utun_instance.h | 10 + 5 files changed, 518 insertions(+), 125 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 350f2481..2875f26f 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -47,7 +47,8 @@ typedef enum { SECTION_NAT, SECTION_TCP_PROXY_CLIENT, SECTION_TCP_PROXY_SERVER, - SECTION_MSG_TRANSPORT + SECTION_MSG_TRANSPORT, + SECTION_NETWORK } section_type_t; static char* trim(char *str) { @@ -716,6 +717,21 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c return -1; } +static int parse_network(const char *key, const char *value, struct CFG_NETWORK *net, const char *filename, int line_num) { + if (strcmp(key, "id") == 0) { + net->id = strtoull(value, NULL, 16); + return 0; + } + if (strcmp(key, "pubkey") == 0) { + return assign_string(net->pubkey_hex, sizeof(net->pubkey_hex), value); + } + if (strcmp(key, "signing_key") == 0) { + return assign_string(net->signing_key_hex, sizeof(net->signing_key_hex), value); + } + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown network option '%s'. Valid: id, pubkey, signing_key", filename, line_num, key); + return -1; +} + static section_type_t parse_section_header(const char *line, char *name, size_t name_len) { if (line[0] != '[') return SECTION_UNKNOWN; @@ -752,7 +768,8 @@ static section_type_t parse_section_header(const char *line, char *name, size_t if (strcasecmp(type, "server") == 0) return SECTION_SERVER; if (strcasecmp(type, "client") == 0) return SECTION_CLIENT; - + if (strcasecmp(type, "network") == 0) return SECTION_NETWORK; + return SECTION_UNKNOWN; } @@ -779,6 +796,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) section_type_t cur_section = SECTION_UNKNOWN; struct CFG_SERVER *cur_server = NULL; struct CFG_CLIENT *cur_client = NULL; + struct CFG_NETWORK *cur_network = NULL; char line[MAX_LINE_LEN]; int line_num = 0; @@ -802,6 +820,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) cfg->clients = cur_client; cur_client = NULL; } + if (cur_section == SECTION_NETWORK && cur_network) { + cur_network->next = cfg->networks; + cfg->networks = cur_network; + cur_network = NULL; + } char name[MAX_CONN_NAME_LEN]; cur_section = parse_section_header(trimmed, name, sizeof(name)); @@ -821,6 +844,13 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) goto error; } strcpy(cur_client->name, name); + } else if (cur_section == SECTION_NETWORK) { + cur_network = u_calloc(1, sizeof(struct CFG_NETWORK)); + if (!cur_network) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for network %s", name); + goto error; + } + strcpy(cur_network->name, name); } continue; } @@ -927,6 +957,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) // parse_msg_transport already printed the error } break; + case SECTION_NETWORK: + if (cur_network && parse_network(key, value, cur_network, filename, line_num) < 0) { + // parse_network already printed the error + } + break; default: DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key); break; @@ -942,6 +977,10 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) cur_client->next = cfg->clients; cfg->clients = cur_client; } + if (cur_section == SECTION_NETWORK && cur_network) { + cur_network->next = cfg->networks; + cfg->networks = cur_network; + } // Post-parse defaults: tun_enabled=1 if not explicitly disabled if (!cfg->global.tun_enabled && cfg->global.tun_ifname[0] == '\0' && cfg->global.tun_ip.family == AF_UNSPEC) { @@ -965,6 +1004,7 @@ error: free_cfg_client_links(cur_client->links); u_free(cur_client); } + if (cur_network) u_free(cur_network); free_config(cfg); return NULL; } @@ -1003,6 +1043,14 @@ void free_config(struct utun_config *config) { client = next; } + // Free networks + struct CFG_NETWORK *network = config->networks; + while (network) { + struct CFG_NETWORK *next = network->next; + u_free(network); + network = next; + } + // Free route entries free_route_entries(config->route_subnets); free_route_entries(config->my_subnets); @@ -1097,8 +1145,16 @@ void print_config(const struct utun_config *cfg) { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s", ip_str); } else { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s:%d", ip_str, r->port); - } - } + } + + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Networks:"); + struct CFG_NETWORK *net = cfg->networks; + while (net) { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " [network:%s] id=%012llx pubkey=%.64s signing_key=%.64s", + net->name, (unsigned long long)net->id, net->pubkey_hex, net->signing_key_hex); + net = net->next; + } +} } else { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules"); } diff --git a/src/config_parser.h b/src/config_parser.h index e416ff48..6ef955ee 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -62,6 +62,14 @@ struct CFG_CLIENT { struct CFG_CLIENT *next; // Next client in linked list }; +struct CFG_NETWORK { + struct CFG_NETWORK* next; + char name[64]; + uint64_t id; // 56-bit network ID + char pubkey_hex[SC_PUBKEY_SIZE * 2 + 1]; // 64 hex chars + char signing_key_hex[SC_PRIVKEY_SIZE * 2 + 1]; // 64 hex chars +}; + struct CFG_ROUTE_ENTRY { struct CFG_ROUTE_ENTRY* next; struct IP ip; @@ -180,6 +188,7 @@ struct utun_config { struct CFG_CLIENT* clients; struct CFG_ROUTE_ENTRY* route_subnets; struct CFG_ROUTE_ENTRY* my_subnets; + struct CFG_NETWORK* networks; }; struct utun_config* parse_config(const char *filename); diff --git a/src/conn_mgr.h b/src/conn_mgr.h index 0ace6bb1..67754278 100644 --- a/src/conn_mgr.h +++ b/src/conn_mgr.h @@ -13,178 +13,427 @@ struct ETCP_CONN; struct NODEINFO_Q; struct ROUTE_BGP; -#define CONN_MGR_MAX_CANDIDATES 3 -#define CONN_MGR_CANDIDATE_CACHE_MS 20000 -#define CONN_MGR_CANDIDATE_STALE_TB 300000 -#define CONN_MGR_CANDIDATE_PING_TB 20000 -#define CONN_MGR_BG_PING_INTERVAL_TB 1000 -#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000 -#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000 -#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3 -#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100 -#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000 -#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000 -#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000 - -#define CONN_MGR_OK 0 -#define CONN_MGR_ERR_NOT_FOUND -1 -#define CONN_MGR_ERR_NO_ADDRESSES -2 -#define CONN_MGR_ERR_TIMEOUT -3 -#define CONN_MGR_ERR_UNREACHABLE -4 -#define CONN_MGR_ERR_REFUSED -5 -#define CONN_MGR_ERR_ALREADY_CONNECTED -6 -#define CONN_MGR_ERR_INTERNAL -7 - +/** + * @file conn_mgr.h + * @brief Менеджер установки соединений. Трёхфазная машина подключения к нодам + * с выбором оптимального пути: прямой → обратный → через посредников. + * + * Основные возможности: + * - Автоматический выбор лучшего способа подключения к целевой ноде + * - Фаза 1 (DIRECT): попытка прямого INIT-рукопожатия на все известные IPv4-адреса цели. + * Итерация по парам (наш сокет, целевой адрес) с проверкой NAT-совместимости. + * Таймаут: CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS + * - Фаза 2 (REVERSE): если у нас есть прямой IP, а у цели нет — отправляем DIRECT_REQ + * с нашими адресами через BGP-маршрут. Цель сама инициирует INIT в нашу сторону. + * Таймаут: CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS + * - Фаза 3 (INDIRECT): если DIRECT и REVERSE не сработали — обмениваемся через BGP + * списками лучших промежуточных узлов-кандидатов с RTT, выбираем общих с минимальным + * суммарным RTT. Трафик идёт через etcp_router (транзитная ретрансляция). + * Таймаут: CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS + * - Фоновое сканирование локальной сети (broadcast/multicast UDP-ping) + * - Фоновое поддержание списка лучших кандидатов-посредников (периодический ping, + * удаление stale-кандидатов) + * - Фоновый обход всех известных BGP-нод для поддержания RTT-замеров + * - Управление idle-таймаутом с автоматическим разрывом неактивных соединений + * + * @note Правила использования: + * 1. conn_mgr_init() вызывается один раз при старте экземпляра utun + * 2. conn_mgr_connect_node() — асинхронный вызов, результат приходит в callback + * 3. Повторный вызов conn_mgr_connect_node для уже подключённой ноды вернёт + * CONN_MGR_ERR_ALREADY_CONNECTED; для ноды в состоянии CONNECTING добавит + * дополнительный callback в список + * 4. conn_mgr_disconnect_node() отправляет DISCONNECT-пакет целевой ноде и + * сбрасывает conn_mgr_type в NODEINFO_Q + * 5. Для alien-нод (подключившихся к нам самостоятельно) используется + * conn_mgr_add_alien_node() вместо conn_mgr_connect_node() + * 6. conn_mgr_update_best_candidates() вызывается извне (обычно из route_connectivity) + * для обновления RTT кандидатов + * + * @note Внутренние таймеры (всё в timebase-единицах, 1 tb = 0.1 мс): + * - bg_ping_timer: фоновый ping нод (CONN_MGR_BG_PING_INTERVAL_TB) + * - candidate_ping_timer: обновление RTT кандидатов (CONN_MGR_CANDIDATE_PING_TB) + * - idle_timer: проверка неактивности подключённых нод (CONN_MGR_IDLE_CHECK_INTERVAL_TB) + */ + +/* ==================== Константы и лимиты ==================== */ + +#define CONN_MGR_MAX_CANDIDATES 3 ///< Макс. число лучших кандидатов-посредников +#define CONN_MGR_CANDIDATE_CACHE_MS 20000 ///< Время жизни кандидата в кеше (мс) — не используется напрямую в .h +#define CONN_MGR_CANDIDATE_STALE_TB 300000 ///< Порог устаревания кандидата (tb), после которого удаляется из списка +#define CONN_MGR_CANDIDATE_PING_TB 20000 ///< Интервал фонового ping кандидатов (tb, ~2 сек) +#define CONN_MGR_BG_PING_INTERVAL_TB 1000 ///< Интервал фонового ping BGP-нод (tb, ~100 мс) +#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000 ///< Минимальная длительность полного цикла обхода нод (tb, ~10 сек) +#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000 ///< Интервал проверки idle-таймаута (tb, ~1 сек) +#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3 ///< Число попыток локального сканирования +#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100 ///< Таймаут локального сканирования (мс) +#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000 ///< Таймаут фазы DIRECT (мс) +#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000 ///< Таймаут фазы REVERSE (мс) +#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000 ///< Таймаут фазы INDIRECT (мс) + +/* ==================== Коды возврата ==================== */ + +#define CONN_MGR_OK 0 ///< Успех +#define CONN_MGR_ERR_NOT_FOUND -1 ///< Нода не найдена в BGP +#define CONN_MGR_ERR_NO_ADDRESSES -2 ///< Нет известных адресов для подключения +#define CONN_MGR_ERR_TIMEOUT -3 ///< Таймаут соединения +#define CONN_MGR_ERR_UNREACHABLE -4 ///< Нода недостижима (все фазы провалились) +#define CONN_MGR_ERR_REFUSED -5 ///< Соединение отклонено (зарезервировано) +#define CONN_MGR_ERR_ALREADY_CONNECTED -6 ///< Уже подключены +#define CONN_MGR_ERR_INTERNAL -7 ///< Внутренняя ошибка + +/* ==================== Состояния ==================== */ + +/** + * @enum CONN_MGR_STATE + * @brief Состояние подключения к ноде. + */ enum CONN_MGR_STATE { - CONN_MGR_STATE_DISCONNECTED = 0, - CONN_MGR_STATE_CONNECTING = 1, - CONN_MGR_STATE_CONNECTED = 2, + CONN_MGR_STATE_DISCONNECTED = 0, ///< Не подключены + CONN_MGR_STATE_CONNECTING = 1, ///< В процессе установки соединения + CONN_MGR_STATE_CONNECTED = 2, ///< Подключены }; +/** + * @enum CM_TRY_STATE + * @brief Состояние отдельной попытки (локальное сканирование / основное подключение). + */ enum CM_TRY_STATE { - CM_TRY_NONE = 0, - CM_TRY_PENDING = 1, - CM_TRY_OK = 2, - CM_TRY_FAILED = 3, + CM_TRY_NONE = 0, ///< Попытка не начиналась + CM_TRY_PENDING = 1, ///< Попытка в процессе + CM_TRY_OK = 2, ///< Попытка успешна + CM_TRY_FAILED = 3, ///< Попытка провалилась }; -#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01 -#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02 -#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03 -#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04 -#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05 -#define CONN_MGR_SUBCMD_DISCONNECT 0x06 - +/* ==================== Протокол CONN_MGR (ETCP_RT_ID_CONN_MGR = 0x11) ==================== */ + +/** + * @defgroup conn_mgr_subcmd Подкоманды протокола CONN_MGR + * @{ + */ +#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01 ///< "Я за NAT, подключись ко мне" — передача наших адресов цели +#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02 ///< Ответ на DIRECT_REQ (зарезервирован, не реализован) +#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03 ///< Запрос обмена кандидатами-посредниками +#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04 ///< Ответ с нашими кандидатами + замеры RTT до кандидатов инициатора +#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05 ///< Выбранные посредники (финальный шаг INDIRECT-фазы) +#define CONN_MGR_SUBCMD_DISCONNECT 0x06 ///< Уведомление о разрыве соединения +/** @} */ + +/** + * @struct CONN_MGR_CANDIDATE + * @brief Запись о кандидате-посреднике: идентификатор ноды + RTT до неё. + * + * Используется в списке best_candidates и во всех пакетах обмена кандидатами. + */ struct CONN_MGR_CANDIDATE { - uint64_t node_id; - uint16_t rtt; + uint64_t node_id; ///< Идентификатор ноды-кандидата + uint16_t rtt; ///< RTT до кандидата (мс) } __attribute__((packed)); #pragma pack(push, 1) + +/** + * @struct CONN_MGR_DIRECT_REQ + * @brief Пакет фазы REVERSE: "подключись ко мне". + * + * Отправляется когда мы имеем прямой IP, но цель за NAT. + * Содержит наши адреса, по которым цель должна инициировать INIT. + */ struct CONN_MGR_DIRECT_REQ { - uint8_t cmd; - uint8_t subcmd; - uint32_t request_id; - uint8_t addr_count; + uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR + uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_REQ + uint32_t request_id; ///< Идентификатор запроса для сопоставления ответа + uint8_t addr_count; ///< Количество адресов, следующих за заголовком + /* далее: addr_count записей по 8 байт каждая (type:1, ip:4, port:2, sock_id:1) */ }; +/** + * @struct CONN_MGR_DIRECT_RESP + * @brief Ответ на DIRECT_REQ (зарезервирован, не отправляется). + */ struct CONN_MGR_DIRECT_RESP { - uint8_t cmd; - uint8_t subcmd; - uint32_t request_id; - uint8_t accepted; + uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR + uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_RESP + uint32_t request_id; ///< Идентификатор запроса + uint8_t accepted; ///< 1 — принято, 0 — отклонено }; +/** + * @struct CONN_MGR_INTERM_EXCHANGE_REQ + * @brief Запрос обмена списками кандидатов-посредников (фаза INDIRECT, инициатор). + * + * Инициатор отправляет свой топ-4 кандидатов. Цель должна ответить + * CONN_MGR_INTERM_EXCHANGE_RESP со своими кандидатами и замерами RTT до наших. + */ struct CONN_MGR_INTERM_EXCHANGE_REQ { - uint8_t cmd; - uint8_t subcmd; - uint32_t request_id; - uint8_t candidate_count; - struct CONN_MGR_CANDIDATE candidates[4]; + uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR + uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ + uint32_t request_id; ///< Идентификатор запроса + uint8_t candidate_count; ///< Количество кандидатов в массиве (≤4) + struct CONN_MGR_CANDIDATE candidates[4]; ///< Наши лучшие кандидаты с RTT }; +/** + * @struct CONN_MGR_INTERM_EXCHANGE_RESP + * @brief Ответ на INTERM_EXCHANGE_REQ (фаза INDIRECT, цель). + * + * Содержит два списка: + * - my_candidates: лучшие кандидаты цели с RTT до них (измеренным целью) + * - your_candidates: кандидаты инициатора, но с RTT, измеренным со стороны цели + */ struct CONN_MGR_INTERM_EXCHANGE_RESP { - uint8_t cmd; - uint8_t subcmd; - uint32_t request_id; - uint8_t my_count; - uint8_t your_count; - struct CONN_MGR_CANDIDATE my_candidates[4]; - struct CONN_MGR_CANDIDATE your_candidates[4]; + uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR + uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP + uint32_t request_id; ///< Идентификатор запроса + uint8_t my_count; ///< Количество наших кандидатов (≤4) + uint8_t your_count; ///< Количество кандидатов инициатора (≤4) + struct CONN_MGR_CANDIDATE my_candidates[4]; ///< Наши кандидаты + наш RTT до них + struct CONN_MGR_CANDIDATE your_candidates[4]; ///< Кандидаты инициатора + наш RTT до них }; +/** + * @struct CONN_MGR_INTERM_SELECTED + * @brief Финальный выбор посредников (фаза INDIRECT). + * + * Инициатор вычисляет лучших общих посредников по сумме RTT + * и отправляет выбранный список цели. + */ struct CONN_MGR_INTERM_SELECTED { - uint8_t cmd; - uint8_t subcmd; - uint32_t request_id; - uint8_t count; - struct CONN_MGR_CANDIDATE selected[3]; + uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR + uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_SELECTED + uint32_t request_id; ///< Идентификатор запроса (всегда 0 для извещения) + uint8_t count; ///< Количество выбранных посредников (≤3) + struct CONN_MGR_CANDIDATE selected[3]; ///< Выбранные посредники (node_id + RTT от нас до них) }; +/** + * @struct CONN_MGR_DISCONNECT + * @brief Уведомление о разрыве соединения. + */ struct CONN_MGR_DISCONNECT { - uint8_t cmd; - uint8_t subcmd; - uint64_t node_id; + uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR + uint8_t subcmd; ///< CONN_MGR_SUBCMD_DISCONNECT + uint64_t node_id; ///< Идентификатор ноды, с которой разрываем соединение }; + #pragma pack(pop) -#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ) -#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP) -#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ) -#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP) -#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED) -#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT) +/* ==================== Размеры пакетов протокола ==================== */ +#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ) ///< Размер заголовка DIRECT_REQ (без addr_count адресов) +#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP) ///< Размер DIRECT_RESP +#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ) ///< Размер INTERM_EXCHANGE_REQ +#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP) ///< Размер INTERM_EXCHANGE_RESP (макс.) +#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED) ///< Размер INTERM_SELECTED +#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT) ///< Размер DISCONNECT + +/* ==================== Callback-типы и внутренние структуры ==================== */ + +/** + * @typedef conn_mgr_connect_callback_t + * @brief Callback результата подключения. + * @param result код возврата (CONN_MGR_OK или CONN_MGR_ERR_*) + * @param node_id идентификатор ноды + * @param arg пользовательский аргумент + */ typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg); +/** + * @struct cm_cb_node + * @brief Узел связного списка callback'ов для одной ноды. + * + * Позволяет регистрировать несколько callback'ов на одно подключение. + * Новые callback'и добавляются в начало списка. + */ struct cm_cb_node { - conn_mgr_connect_callback_t cb; - void* arg; - struct cm_cb_node* next; + conn_mgr_connect_callback_t cb; ///< Функция-callback + void* arg; ///< Пользовательский аргумент + struct cm_cb_node* next; ///< Следующий callback в списке }; +/** + * @struct CONN_MGR_ENTRY + * @brief Запись о подключении к одной ноде. + * + * Хранит состояние трёхфазной машины, список выбранных посредников, + * idle-таймер и список ожидающих callback'ов. + */ struct CONN_MGR_ENTRY { - uint64_t node_id; - uint8_t state; - uint8_t conn_type; - uint8_t alien; - uint32_t idle_timeout_ms; - uint64_t last_traffic_tb; - void* idle_timer; - - struct cm_cb_node* cb_list; - - uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; - uint8_t intermediariy_count; - uint8_t rr_idx; - - uint8_t local_scan_state; - uint8_t main_connect_state; + uint64_t node_id; ///< Идентификатор целевой ноды + uint8_t state; ///< Текущее состояние (enum CONN_MGR_STATE) + uint8_t conn_type; ///< Тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT) + uint8_t alien; ///< 1 — alien-нода (сама подключилась к нам), 0 — мы инициаторы + uint32_t idle_timeout_ms; ///< Таймаут неактивности (мс), 0 — без таймаута + uint64_t last_traffic_tb; ///< Время последнего трафика (tb) для idle-проверки + void* idle_timer; ///< Хендл таймера idle-проверки + + struct cm_cb_node* cb_list; ///< Связный список callback'ов результата + + uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; ///< Выбранные посредники (node_id) + uint8_t intermediariy_count; ///< Количество выбранных посредников + uint8_t rr_idx; ///< Round-robin индекс для выбора посредника при отправке + + uint8_t local_scan_state; ///< Состояние локального сканирования (enum CM_TRY_STATE) + uint8_t main_connect_state; ///< Состояние основного подключения (enum CM_TRY_STATE) struct { - uint8_t phase; - void* timer; - uint32_t request_id; - void* conn_ctx; + uint8_t phase; ///< Текущая фаза: 1=DIRECT, 2=REVERSE, 3=INDIRECT + void* timer; ///< Хендл таймера текущей фазы + uint32_t request_id; ///< request_id для INDIRECT-обмена + void* conn_ctx; ///< Контекст ETCP-соединения (для REVERSE) } main; - struct CONN_MGR* mgr; + struct CONN_MGR* mgr; ///< Обратная ссылка на менеджер }; +/** + * @struct CONN_MGR + * @brief Корневая структура менеджера соединений. + * + * Создаётся один раз в conn_mgr_init(), хранится в UTUN_INSTANCE. + * Содержит динамический массив entries, список лучших кандидатов, + * таймеры фонового ping и список ожидающих обменов (reverse/exchange pending). + */ struct CONN_MGR { - struct UTUN_INSTANCE* instance; - struct CONN_MGR_ENTRY* entries; - size_t entry_count; - size_t entry_capacity; + struct UTUN_INSTANCE* instance; ///< Родительский экземпляр utun - void* bg_ping_timer; - size_t bg_ping_cursor; - uint64_t bg_ping_cycle_start_tb; + struct CONN_MGR_ENTRY* entries; ///< Динамический массив записей о подключениях + size_t entry_count; ///< Текущее количество записей + size_t entry_capacity; ///< Выделенная ёмкость массива entries - void* candidate_ping_timer; + void* bg_ping_timer; ///< Хендл таймера фонового ping BGP-нод + size_t bg_ping_cursor; ///< Курсор текущей ноды в цикле обхода + uint64_t bg_ping_cycle_start_tb; ///< Время старта текущего цикла обхода (tb) - struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; - uint8_t best_candidate_count; + void* candidate_ping_timer; ///< Хендл таймера обновления RTT кандидатов - uint32_t next_request_id; - uint8_t initialized; + struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; ///< Топ кандидатов-посредников, отсортированных по RTT + uint8_t best_candidate_count; ///< Количество актуальных кандидатов (≤CONN_MGR_MAX_CANDIDATES) - struct cm_reverse_pending* reverse_pending; - struct cm_exchange_pending* exchange_pending; + uint32_t next_request_id; ///< Счётчик request_id для INTERM_EXCHANGE + uint8_t initialized; ///< Флаг инициализации + + struct cm_reverse_pending* reverse_pending; ///< Связный список ожидающих REVERSE-подключений + struct cm_exchange_pending* exchange_pending; ///< Связный список ожидающих INDIRECT-обменов }; +/* ==================== Жизненный цикл ==================== */ + +/** + * @brief Инициализирует менеджер соединений. + * @param instance родительский экземпляр utun + * @return указатель на CONN_MGR или NULL при ошибке + * + * Регистрирует обработчик в etcp_router для ETCP_RT_ID_CONN_MGR, + * запускает фоновые таймеры (bg_ping, candidate_ping). + */ struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance); -void conn_mgr_destroy(struct CONN_MGR* mgr); - -int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, - uint32_t idle_timeout_ms, - conn_mgr_connect_callback_t cb, void* cb_arg); -int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id); -int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms); -int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, - uint8_t* out_state, uint8_t* out_conn_type); -int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len); -int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry); +/** + * @brief Уничтожает менеджер соединений. + * @param mgr менеджер + * + * Освобождает все записи, таймеры, pending-структуры. + * Вызывает cm_clear_nodeinfo для каждой записи. + */ +void conn_mgr_destroy(struct CONN_MGR* mgr); + +/* ==================== Подключение / отключение ==================== */ + +/** + * @brief Запускает асинхронное подключение к ноде. + * @param mgr менеджер + * @param node_id идентификатор целевой ноды + * @param idle_timeout_ms таймаут неактивности (мс), 0 = без таймаута + * @param cb callback результата (CONN_MGR_OK, CONN_MGR_ERR_TIMEOUT, CONN_MGR_ERR_UNREACHABLE и т.д.) + * @param cb_arg пользовательский аргумент для callback + * @return CONN_MGR_OK если процесс запущен, CONN_MGR_ERR_* при ошибке + * + * @note Если нода уже подключена — сразу вызывает callback с CONN_MGR_OK и возвращает CONN_MGR_OK. + * @note Если нода в процессе подключения — добавляет callback в список ожидания. + * @note Автоматически определяет наличие BGP-соединения (уже существующий ETCP-линк). + * @note Для alien-нод используйте conn_mgr_add_alien_node(). + */ +int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, + uint32_t idle_timeout_ms, + conn_mgr_connect_callback_t cb, void* cb_arg); + +/** + * @brief Разрывает подключение к ноде. + * @param mgr менеджер + * @param node_id идентификатор ноды + * @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND + * + * Отправляет DISCONNECT-пакет целевой ноде, сбрасывает conn_mgr_type в NODEINFO_Q, + * уничтожает запись entry. + */ +int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id); + +/* ==================== Управление idle-таймаутом ==================== */ + +/** + * @brief Устанавливает idle-таймаут для подключённой ноды. + * @param mgr менеджер + * @param node_id идентификатор ноды + * @param timeout_ms таймаут неактивности (мс), 0 = отключить проверку + * @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND / CONN_MGR_ERR_INTERNAL + * + * Если таймаут > 0 и нода в состоянии CONNECTED — запускает периодический таймер. + * При отсутствии трафика в течение timeout_ms соединение автоматически разрывается. + */ +int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms); + +/** + * @brief Получает текущий статус подключения к ноде. + * @param mgr менеджер + * @param node_id идентификатор ноды + * @param out_state [out] состояние (enum CONN_MGR_STATE) + * @param out_conn_type [out] тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT) + * @return CONN_MGR_OK + */ +int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, + uint8_t* out_state, uint8_t* out_conn_type); + +/* ==================== Alien-ноды ==================== */ + +/** + * @brief Регистрирует alien-ноду (подключившуюся к нам самостоятельно). + * @param mgr менеджер + * @param nodeinfo_data сырые данные NODEINFO (BGP-пакет) + * @param len длина данных + * @return CONN_MGR_OK или CONN_MGR_ERR_INTERNAL + * + * Alien-нода — это нода, которая сама инициировала INIT-подключение к нам. + * Для неё не запускаются фазы подключения, сразу устанавливается CONN_MGR_STATE_CONNECTED. + * Создаёт entry с флагом alien=1. + */ +int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len); + +/** + * @brief Отправляет данные подключённой ноде через etcp_router. + * @param mgr менеджер + * @param node_id идентификатор целевой ноды + * @param entry ll_entry с данными для отправки (освобождается после отправки) + * @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND + * + * Для INDIRECT-соединений пакет автоматически маршрутизируется через + * выбранного посредника (round-robin по intermediaries[]). + * Обновляет last_traffic_tb для idle-проверки. + */ +int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry); + +/* ==================== Управление кандидатами ==================== */ + +/** + * @brief Обновляет RTT кандидата в списке лучших посредников. + * @param mgr менеджер + * @param node_id идентификатор ноды-кандидата + * @param rtt RTT до кандидата (мс) + * + * Если кандидат уже в списке — обновляет RTT. Если нет — вставляет с сортировкой + * по возрастанию RTT. Если список заполнен и новый кандидат имеет худший RTT — + * игнорируется. Список всегда отсортирован: best_candidates[0] — наименьший RTT. + * + * @note Обычно вызывается из route_connectivity после успешного probe. + */ void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt); #endif diff --git a/src/utun_instance.c b/src/utun_instance.c index a8650ddd..1f03f616 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -27,6 +27,7 @@ #include #include "../lib/platform_compat.h" #include "../lib/mem.h" +#include "../lib/ll_queue.h" @@ -85,6 +86,35 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u return -1; } + // Initialize networks queue (indexed by 56-bit network id) + instance->networks = queue_new(ua, 16, offsetof(struct NETWORK_ENTRY, id), 7, "networks"); + if (!instance->networks) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to create networks queue"); + return -1; + } + struct CFG_NETWORK *net = config->networks; + while (net) { + struct ll_entry *entry = queue_entry_new(sizeof(struct NETWORK_ENTRY)); + if (!entry) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate network entry for %s", net->name); + net = net->next; + continue; + } + struct NETWORK_ENTRY *ne = (struct NETWORK_ENTRY*)entry->data; + ne->id = net->id & 0x00FFFFFFFFFFFFFFULL; + for (int i = 0; i < SC_PUBKEY_SIZE; i++) { + unsigned int b; sscanf(net->pubkey_hex + i * 2, "%2x", &b); ne->pubkey[i] = (uint8_t)b; + } + for (int i = 0; i < SC_PRIVKEY_SIZE; i++) { + unsigned int b; sscanf(net->signing_key_hex + i * 2, "%2x", &b); ne->signing_key[i] = (uint8_t)b; + } + strncpy(ne->name, net->name, sizeof(ne->name) - 1); + ne->name[sizeof(ne->name) - 1] = '\0'; + queue_data_put_with_index(instance->networks, entry); + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Network added: [%s] id=%012llx", ne->name, (unsigned long long)ne->id); + net = net->next; + } + // Create memory pools instance->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool"); instance->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool"); @@ -408,6 +438,16 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { instance->stcp_server = NULL; } + // Cleanup networks queue + if (instance->networks) { + struct ll_entry *entry; + while ((entry = queue_data_get(instance->networks)) != NULL) { + queue_entry_free(entry); + } + queue_free(instance->networks); + instance->networks = NULL; + } + // Cleanup config if (instance->config) { DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration"); @@ -754,6 +794,35 @@ struct UTUN_INSTANCE *utun_instance_reload(struct UTUN_INSTANCE *instance, struc l = next_l; } } + // Reload networks: clear and repopulate + if (instance->networks) { + struct ll_entry *entry; + while ((entry = queue_data_get(instance->networks)) != NULL) { + queue_entry_free(entry); + } + struct CFG_NETWORK *net = new_config->networks; + while (net) { + struct ll_entry *new_entry = queue_entry_new(sizeof(struct NETWORK_ENTRY)); + if (!new_entry) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate network entry for reload: %s", net->name); + net = net->next; + continue; + } + struct NETWORK_ENTRY *ne = (struct NETWORK_ENTRY*)new_entry->data; + ne->id = net->id & 0x00FFFFFFFFFFFFFFULL; + for (int i = 0; i < SC_PUBKEY_SIZE; i++) { + unsigned int b; sscanf(net->pubkey_hex + i * 2, "%2x", &b); ne->pubkey[i] = (uint8_t)b; + } + for (int i = 0; i < SC_PRIVKEY_SIZE; i++) { + unsigned int b; sscanf(net->signing_key_hex + i * 2, "%2x", &b); ne->signing_key[i] = (uint8_t)b; + } + strncpy(ne->name, net->name, sizeof(ne->name) - 1); + ne->name[sizeof(ne->name) - 1] = '\0'; + queue_data_put_with_index(instance->networks, new_entry); + net = net->next; + } + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Networks reloaded"); + } fw_free(&instance->fw); fw_init(&instance->fw); fw_load_rules(&instance->fw, &new_config->global); diff --git a/src/utun_instance.h b/src/utun_instance.h index baa9c502..36120a85 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -36,6 +36,13 @@ struct PING_CONTEXT; struct CONN_MGR; struct ETCP_CONNECT; +struct NETWORK_ENTRY { + uint64_t id; // 56-bit (offset 0 = index key) + uint8_t pubkey[SC_PUBKEY_SIZE]; // 32 bytes binary + uint8_t signing_key[SC_PRIVKEY_SIZE]; // 32 bytes binary + char name[64]; +}; + // uTun instance configuration struct UTUN_INSTANCE { // Identification @@ -119,6 +126,9 @@ struct UTUN_INSTANCE { // TCP proxy server (exit node) struct tcp_proxy_server tcp_proxy_server; + // Networks (queue of NETWORK_ENTRY, indexed by 56-bit id) + struct ll_queue* networks; + // Pending background connections (etcp_connect API) struct ETCP_CONNECT* pending_connects; uint32_t etcp_connect_timeout_tb; // Initial timeout in 0.1ms units, default 20000 (2s)