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.
523 lines
29 KiB
523 lines
29 KiB
/* |
|
* topo_group_connect.c — авто-подключение к узлам группы (бесконечный цикл до успеха) |
|
* |
|
* Phase 1: одновременный запуск node_conn_direct_open для пиров с connected=1. |
|
* Таймаут = TGC_DIRECT_TIMEOUT_MS. Если хоть один подключился → done. |
|
* |
|
* Phase 2: последовательный перебор (supernode, затем public/EIM адреса). |
|
* После каждого TIMEOUT — пауза перед следующей попыткой. |
|
* |
|
* Phase 3: последовательный перебор локальных/strict NAT адресов. |
|
* После каждого TIMEOUT — пауза перед следующей попыткой. |
|
* |
|
* После исчерпания Phase 3 → пауза → cycle_restart → Phase 1 (бесконечно). |
|
* |
|
* Пауза: активный режим — 1с; фоновый (Android standby) — standby_wait |
|
* (burst → сразу, sleep → до следующего burst). |
|
* При DOWN (active_conn_count → 0) — немедленный cycle_restart. |
|
* При повторном topo_group_connect_init — destroy + fresh init. |
|
* |
|
* Счётчик active_conn_count — число уникальных peer-узлов с реальными соединениями. |
|
* UP: инкремент только для первого соединения к peer_node_id. |
|
* DOWN: декремент только если нет других conn к peer_node_id И нет indirect-путей. |
|
*/ |
|
|
|
#include "topo_group_connect.h" |
|
#include "topo_group.h" |
|
#include "topo_node.h" |
|
#include "topo_node_sqlite.h" |
|
#include "../transport_layer/node_conn_direct.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
#include "../lib/u_async.h" |
|
#include "../lib/ll_queue.h" |
|
#include "etcp.h" |
|
#include "../chat/chat_event.h" |
|
#ifdef UTUN_HAVE_STANDBY |
|
#include "standby.h" |
|
#endif |
|
|
|
/* ─── внутренние константы ─── */ |
|
#define TGC_ID "topo_group_connect" |
|
#define TGC_DIRECT_TIMEOUT_MS 2000 |
|
#define TGC_PHASE_ONE 0 |
|
#define TGC_PHASE_TWO 1 |
|
#define TGC_PHASE_THREE 2 |
|
#define TGC_PHASE_DONE 3 |
|
#define TGC_PHASE_PAUSE 4 |
|
#define TGC_MAX_HANDLES 128 |
|
#define TGC_PAUSE_ACTIVE_TB 10000 /* 1s пауза в активном режиме */ |
|
|
|
struct TOPO_GROUP_CONNECT { |
|
struct TOPO_GROUP* group; |
|
void* phase_timer; |
|
void* pause_timer; /* uasync timeout handle (активный режим) */ |
|
void* pause_wait; /* standby_wait handle (фоновый режим, Android) */ |
|
uint8_t phase; |
|
int pending; |
|
int connected_count; |
|
int active_conn_count; |
|
int cursor; |
|
int tried_super; |
|
uint64_t* candidate_ids; |
|
int candidate_count; |
|
struct NODE_CONN_DIRECT* handles[TGC_MAX_HANDLES]; |
|
int handle_count; |
|
struct NODE_CONN_DIRECT* cur_handle; /* handle текущей попытки Phase 2/3 (для закрытия на TIMEOUT) */ |
|
}; |
|
|
|
static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc); |
|
static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc); |
|
static void tgc_phase1_timeout(void* arg); |
|
static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc); |
|
static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label); |
|
|
|
static void tgc_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg); |
|
|
|
/* ─── нотификация GUI о списке узлов в процессе подключения ─── */ |
|
|
|
/** |
|
* Отправляет в GUI событие CHAT_EVT_CONNECTING_NODES — список узлов, к которым |
|
* сейчас идёт попытка подключения. GUI по нему рисует жёлтый кружок у этих узлов. |
|
* Пустой список (count=0) означает «сейчас ни к кому не подключаемся» — |
|
* все жёлтые кружки снимаются. |
|
*/ |
|
static void tgc_notify_connecting(struct TOPO_GROUP_CONNECT* gc, const uint64_t* ids, int count) { |
|
size_t cl = strlen(gc->group->channel_id); if (cl > 255) cl = 255; |
|
size_t sz = 1 + cl + 2 + (size_t)count * 8; |
|
uint8_t* buf = u_malloc(sz); if (!buf) return; |
|
uint8_t* p = buf; |
|
*p++ = (uint8_t)cl; memcpy(p, gc->group->channel_id, cl); p += cl; |
|
uint16_t c = (uint16_t)count; memcpy(p, &c, 2); p += 2; |
|
for (int i = 0; i < count; i++) { memcpy(p, &ids[i], 8); p += 8; } |
|
chat_event_post(CHAT_EVT_CONNECTING_NODES, buf, (int)sz); |
|
u_free(buf); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: notify_connecting ch=%s count=%d", |
|
TGC_ID, gc->group->channel_id, count); |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* Жизненный цикл |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Запуск авто-подключения к узлам CHAT-группы. |
|
* Если модуль уже запущен — останавливает его и стартует заново с нуля. |
|
* Начинает с Phase 1: параллельные попытки ко всем пирам, что были connected |
|
* в прошлой сессии (из БД). Если таких нет — сразу переходит к Phase 2. |
|
*/ |
|
int topo_group_connect_init(struct TOPO_GROUP* group) { |
|
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0]) |
|
return -1; |
|
if (group->connect) topo_group_connect_destroy(group); |
|
struct TOPO_GROUP_CONNECT* gc = u_calloc(1, sizeof(*gc)); |
|
if (!gc) return -1; |
|
gc->group = group; gc->tried_super = 0; |
|
group->connect = gc; |
|
|
|
uint64_t* ids = NULL; int count = 0; |
|
sqlite3* db = group->instance->topo_sqlite_db; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: init ch=%s db=%p", TGC_ID, group->channel_id, (void*)db); |
|
if (!db || topo_node_sqlite_get_connected_peers(db, group->channel_id, &ids, &count) != 0 || count == 0) { |
|
if (ids) { u_free(ids); ids = NULL; } |
|
gc->phase = TGC_PHASE_TWO; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: ch=%s no connected peers → Phase 2", TGC_ID, group->channel_id); |
|
tgc_phase2_try_next(gc); |
|
return 0; |
|
} |
|
|
|
gc->phase = TGC_PHASE_ONE; gc->pending = count; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase 1 launching %d connects ch=%s grp=%016llx", TGC_ID, count, group->channel_id, (unsigned long long)group->group_id); |
|
for (int i = 0; i < count && gc->handle_count < TGC_MAX_HANDLES; i++) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 connect to 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)ids[i], i + 1, count); |
|
node_conn_direct_open(group->instance, ids[i], tgc_callback, gc, |
|
&gc->handles[gc->handle_count++], NULL); |
|
} |
|
tgc_notify_connecting(gc, ids, gc->handle_count); |
|
u_free(ids); |
|
|
|
gc->phase_timer = uasync_set_timeout(group->instance->ua, |
|
TGC_DIRECT_TIMEOUT_MS * 10, gc, tgc_phase1_timeout, "tgc_phase1"); |
|
return 0; |
|
} |
|
|
|
/** |
|
* Полная остановка авто-подключения: отменяет все таймеры, закрывает все |
|
* открытые соединения и освобождает память. Вызывается при удалении группы |
|
* и при перезапуске (init/restart). |
|
*/ |
|
void topo_group_connect_destroy(struct TOPO_GROUP* group) { |
|
struct TOPO_GROUP_CONNECT* gc = group->connect; |
|
if (!gc) return; |
|
group->connect = NULL; |
|
if (gc->phase_timer) { uasync_cancel_timeout(group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; } |
|
if (gc->pause_timer) { uasync_cancel_timeout(group->instance->ua, gc->pause_timer); gc->pause_timer = NULL; } |
|
#ifdef UTUN_HAVE_STANDBY |
|
if (gc->pause_wait) { standby_wait_cancel(gc->pause_wait); gc->pause_wait = NULL; } |
|
#endif |
|
for (int i = 0; i < gc->handle_count; i++) node_conn_direct_close(gc->handles[i]); |
|
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; } |
|
u_free(gc->candidate_ids); |
|
u_free(gc); |
|
} |
|
|
|
/** |
|
* Внешний перезапуск авто-подключения (destroy + init). Используется, например, |
|
* при смене сети, когда надо начать поиск заново. Не трогает модуль, если уже |
|
* есть живые соединения. |
|
*/ |
|
void topo_group_connect_restart(struct TOPO_GROUP* group) { |
|
struct TOPO_GROUP_CONNECT* gc = group->connect; |
|
if (!gc) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: restart ch=%s no gc → init", TGC_ID, group->channel_id); topo_group_connect_init(group); return; } |
|
if (gc->active_conn_count > 0) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: restart ch=%s skip active=%d", TGC_ID, group->channel_id, gc->active_conn_count); return; } |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: restart ch=%s phase=%d", TGC_ID, group->channel_id, gc->phase); |
|
topo_group_connect_destroy(group); |
|
topo_group_connect_init(group); |
|
} |
|
|
|
/** |
|
* Сколько сейчас живых соединений с пирами группы. Нужно внешней логике |
|
* (например, chat_sync) для решения, запускать ли переподключение. |
|
*/ |
|
int topo_group_connect_active_count(struct TOPO_GROUP* group) { |
|
struct TOPO_GROUP_CONNECT* gc = group->connect; |
|
return gc ? gc->active_conn_count : 0; |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* ON UP / DOWN |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Обработка появления соединения с пиром. Увеличивает счётчик активных и |
|
* помечает connected=1 в БД (чтобы при следующем старте узел попал в Phase 1). |
|
* Пропускает, если пир уже подключён через другое соединение. |
|
*/ |
|
void topo_group_connect_on_up(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { |
|
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || !conn) return; |
|
uint64_t peer = conn->peer_node_id; |
|
struct TOPO_GROUP_CONNECT* gc = group->connect; |
|
if (!gc) return; |
|
|
|
struct ll_entry* e = group->senders_list ? group->senders_list->head : NULL; |
|
while (e) { |
|
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; |
|
if (item->conn && item->conn != conn && item->conn->peer_node_id == peer) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: UP peer=0x%016llx already connected via other conn, skip count", |
|
TGC_ID, (unsigned long long)peer); |
|
return; |
|
} |
|
e = e->next; |
|
} |
|
|
|
gc->active_conn_count++; |
|
sqlite3* db = group->instance->topo_sqlite_db; |
|
if (db) topo_node_sqlite_set_connected(db, group->channel_id, peer, 1); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: UP peer=0x%016llx ch=%s grp=%016llx active=%d", |
|
TGC_ID, (unsigned long long)peer, group->channel_id, (unsigned long long)group->group_id, gc->active_conn_count); |
|
} |
|
|
|
/** |
|
* Обработка обрыва соединения. Уменьшает счётчик активных и помечает |
|
* connected=0 в БД. Если живых соединений не осталось — немедленно |
|
* перезапускает цикл поиска с Phase 1. |
|
*/ |
|
void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { |
|
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || !conn) return; |
|
uint64_t peer = conn->peer_node_id; |
|
struct TOPO_GROUP_CONNECT* gc = group->connect; |
|
if (!gc) return; |
|
|
|
struct ll_entry* e = group->senders_list ? group->senders_list->head : NULL; |
|
while (e) { |
|
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; |
|
if (item->conn && item->conn != conn && item->conn->peer_node_id == peer) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx still has other direct conn, skip", |
|
TGC_ID, (unsigned long long)peer); |
|
return; |
|
} |
|
e = e->next; |
|
} |
|
|
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, peer); |
|
if (nq && nq->paths) { |
|
struct ll_entry* pe = nq->paths->head; |
|
while (pe) { |
|
struct TOPO_NODEPATH* path = (struct TOPO_NODEPATH*)pe; |
|
if (path->conn && path->conn != conn) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx has indirect path, skip", |
|
TGC_ID, (unsigned long long)peer); |
|
return; |
|
} |
|
pe = pe->next; |
|
} |
|
} |
|
|
|
gc->active_conn_count--; |
|
sqlite3* db = group->instance->topo_sqlite_db; |
|
if (db) topo_node_sqlite_set_connected(db, group->channel_id, peer, 0); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx ch=%s active=%d", |
|
TGC_ID, (unsigned long long)peer, group->channel_id, gc->active_conn_count); |
|
if (gc->active_conn_count == 0) { |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN no connections — cycle restart ch=%s", TGC_ID, group->channel_id); |
|
tgc_cycle_restart(gc); |
|
} |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* Единый коллбэк для всех фаз |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Единый коллбэк на результат каждой попытки подключения (успех/таймаут/обрыв). |
|
* В Phase 1 — просто считает результаты (итог подводит tgc_phase1_timeout). |
|
* В Phase 2/3 — успех завершает цикл; провал закрывает handle (чтобы не оставить |
|
* залипшее состояние) и ставит паузу перед следующей попыткой. |
|
*/ |
|
static void tgc_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg) { |
|
struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg; |
|
struct ETCP_CONN* conn = node_conn_direct_get_conn(h); |
|
uint64_t node_id = conn ? conn->peer_node_id : 0; |
|
|
|
int ok = (event == NCD_EVENT_UP); |
|
|
|
if (ok) { |
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(gc->group, node_id); |
|
if (nq) nq->handle = h; |
|
} else if (event == NCD_EVENT_TIMEOUT) { |
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(gc->group, node_id); |
|
if (nq) nq->handle = NULL; |
|
} |
|
|
|
switch (gc->phase) { |
|
case TGC_PHASE_ONE: |
|
gc->pending--; |
|
if (ok) gc->connected_count++; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 result node=0x%016llx %s pending=%d connected=%d", |
|
TGC_ID, (unsigned long long)node_id, ok ? "OK" : "FAIL", gc->pending, gc->connected_count); |
|
break; |
|
case TGC_PHASE_TWO: |
|
if (ok) { |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); |
|
gc->phase = TGC_PHASE_DONE; |
|
tgc_notify_connecting(gc, NULL, 0); |
|
} else { |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id); |
|
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; } |
|
tgc_start_pause(gc, (void(*)(void*))tgc_phase2_try_next, "tgc_p2_try"); |
|
} |
|
break; |
|
case TGC_PHASE_THREE: |
|
if (ok) { |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); |
|
gc->phase = TGC_PHASE_DONE; |
|
tgc_notify_connecting(gc, NULL, 0); |
|
} else { |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id); |
|
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; } |
|
tgc_start_pause(gc, (void(*)(void*))tgc_phase3_try_next, "tgc_p3_try"); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* Пауза: 1s ACTIVE / 30s STANDBY |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Пауза между попытками подключения. Длительность зависит от активности |
|
* приложения: 1 секунда когда приложение активно, 30 секунд когда в фоне |
|
* (экран выключен). После паузы вызывает cb (следующая попытка или перезапуск |
|
* цикла). На время паузы уведомляет GUI пустым списком (жёлтые кружки гаснут). |
|
*/ |
|
static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label) { |
|
gc->phase = TGC_PHASE_PAUSE; |
|
tgc_notify_connecting(gc, NULL, 0); |
|
#ifdef UTUN_HAVE_STANDBY |
|
if (standby_is_enabled()) { |
|
/* фоновый режим: burst → standby_wait будит сразу, sleep → до следующего burst */ |
|
gc->pause_wait = standby_wait(gc, cb); |
|
gc->pause_timer = NULL; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: pause (standby) for ch=%s", TGC_ID, gc->group->channel_id); |
|
return; |
|
} |
|
#endif |
|
gc->pause_timer = uasync_set_timeout(gc->group->instance->ua, TGC_PAUSE_ACTIVE_TB, gc, cb, label); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: pause %dms (active) for ch=%s", |
|
TGC_ID, TGC_PAUSE_ACTIVE_TB / 10, gc->group->channel_id); |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* Phase 1 timeout |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Таймаут параллельных попыток Phase 1 (2 секунды). Закрывает неудачные |
|
* handles и сбрасывает connected в БД. Если хоть один узел подключился — |
|
* цикл завершён; иначе переходим к Phase 2. |
|
*/ |
|
static void tgc_phase1_timeout(void* arg) { |
|
struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg; |
|
gc->phase_timer = NULL; |
|
if (gc->phase != TGC_PHASE_ONE) return; |
|
|
|
sqlite3* db = gc->group->instance->topo_sqlite_db; |
|
uint64_t* ids = NULL; int count = 0; |
|
topo_node_sqlite_get_connected_peers(db, gc->group->channel_id, &ids, &count); |
|
for (int i = 0; i < count; i++) { |
|
int connected = 0; |
|
for (int j = 0; j < gc->handle_count; j++) { |
|
struct NODE_CONN_DIRECT* h = gc->handles[j]; |
|
if (h && node_conn_direct_get_conn(h) != NULL) { connected = 1; break; } |
|
} |
|
if (!connected) topo_node_sqlite_set_connected(db, gc->group->channel_id, ids[i], 0); |
|
} |
|
u_free(ids); |
|
|
|
int remaining = 0; |
|
for (int i = 0; i < gc->handle_count; i++) { |
|
struct NODE_CONN_DIRECT* h = gc->handles[i]; |
|
if (h && node_conn_direct_get_conn(h) != NULL) { |
|
gc->handles[remaining++] = h; |
|
} else { |
|
if (h) node_conn_direct_close(h); |
|
} |
|
} |
|
gc->handle_count = remaining; |
|
|
|
if (gc->connected_count > 0) { |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — %d connected, skipping Phase 2", TGC_ID, gc->connected_count); |
|
gc->phase = TGC_PHASE_DONE; |
|
tgc_notify_connecting(gc, NULL, 0); |
|
return; |
|
} |
|
gc->phase = TGC_PHASE_TWO; |
|
tgc_phase2_try_next(gc); |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* Phase 2 |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Последовательный перебор узлов с публичными/EIM адресами (сначала supernode, |
|
* затем обычные публичные). Пробует следующий узел и ставит ему жёлтый кружок. |
|
* Когда все перебраны — переходит к Phase 3. |
|
*/ |
|
static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) { |
|
gc->pause_timer = NULL; gc->pause_wait = NULL; |
|
if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_TWO; |
|
if (gc->phase != TGC_PHASE_TWO) return; |
|
while (1) { |
|
if (gc->candidate_count == 0) { |
|
sqlite3* db = gc->group->instance->topo_sqlite_db; |
|
if (gc->tried_super == 0) { |
|
topo_node_sqlite_get_supernode_peers(db, gc->group->channel_id, &gc->candidate_ids, &gc->candidate_count); |
|
gc->tried_super = 1; gc->cursor = 0; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 supernode round — loaded %d for ch=%s", |
|
TGC_ID, gc->candidate_count, gc->group->channel_id); |
|
} else if (gc->tried_super == 1) { |
|
topo_node_sqlite_get_public_peers(db, gc->group->channel_id, &gc->candidate_ids, &gc->candidate_count, |
|
gc->group->instance->node_id); |
|
gc->tried_super = 2; gc->cursor = 0; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 public round — loaded %d for ch=%s", |
|
TGC_ID, gc->candidate_count, gc->group->channel_id); |
|
} else { |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 exhausted — no connections, ch=%s", TGC_ID, gc->group->channel_id); |
|
gc->phase = TGC_PHASE_THREE; gc->candidate_count = 0; |
|
tgc_phase3_try_next(gc); |
|
return; |
|
} |
|
} |
|
if (gc->cursor < gc->candidate_count) { |
|
uint64_t nid = gc->candidate_ids[gc->cursor++]; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 trying 0x%016llx (%d/%d) super_round=%d", TGC_ID, |
|
(unsigned long long)nid, gc->cursor, gc->candidate_count, gc->tried_super); |
|
if (node_conn_direct_open(gc->group->instance, nid, tgc_callback, gc, &gc->cur_handle, NULL) < 0) continue; |
|
tgc_notify_connecting(gc, &nid, 1); |
|
return; |
|
} |
|
if (gc->candidate_ids) { u_free(gc->candidate_ids); gc->candidate_ids = NULL; } |
|
gc->candidate_count = 0; |
|
} |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* Phase 3 — локальные/strict NAT адреса |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Последовательный перебор узлов с локальными/strict NAT адресами (как Phase 2, |
|
* но для узлов, доступных только в локальной сети). Когда все перебраны — |
|
* пауза и полный перезапуск цикла с Phase 1 (бесконечно, пока не подключимся). |
|
*/ |
|
static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) { |
|
gc->pause_timer = NULL; gc->pause_wait = NULL; |
|
if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_THREE; |
|
if (gc->phase != TGC_PHASE_THREE) return; |
|
if (gc->candidate_count == 0) { |
|
sqlite3* db = gc->group->instance->topo_sqlite_db; |
|
topo_node_sqlite_get_local_peers(db, gc->group->channel_id, &gc->candidate_ids, &gc->candidate_count, |
|
gc->group->instance->node_id); |
|
gc->cursor = 0; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 loaded %d local peers for ch=%s", |
|
TGC_ID, gc->candidate_count, gc->group->channel_id); |
|
} |
|
while (gc->cursor < gc->candidate_count) { |
|
uint64_t nid = gc->candidate_ids[gc->cursor++]; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 trying 0x%016llx (%d/%d)", TGC_ID, |
|
(unsigned long long)nid, gc->cursor, gc->candidate_count); |
|
if (node_conn_direct_open(gc->group->instance, nid, tgc_callback, gc, &gc->cur_handle, NULL) < 0) continue; |
|
tgc_notify_connecting(gc, &nid, 1); |
|
return; |
|
} |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 exhausted — pause then cycle restart, ch=%s", TGC_ID, gc->group->channel_id); |
|
tgc_start_pause(gc, (void(*)(void*))tgc_cycle_restart, "tgc_p3_restart"); |
|
} |
|
|
|
/* ═══════════════════════════════════════════════════════════════════════ |
|
* Полный перезапуск цикла с Phase 1 (без destroy/init) |
|
* ══════════════════════════════════════════════════════════════════════ */ |
|
|
|
/** |
|
* Полный перезапуск цикла с Phase 1 без уничтожения модуля. Закрывает все |
|
* открытые соединения, сбрасывает состояние и заново запускает параллельные |
|
* попытки ко всем connected-пирам из БД. Вызывается при обрыве всех соединений |
|
* и после исчерпания Phase 3. |
|
*/ |
|
static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc) { |
|
gc->pause_timer = NULL; gc->pause_wait = NULL; |
|
if (gc->phase_timer) { uasync_cancel_timeout(gc->group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; } |
|
for (int i = 0; i < gc->handle_count; i++) node_conn_direct_close(gc->handles[i]); |
|
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; } |
|
gc->handle_count = 0; gc->pending = 0; gc->connected_count = 0; |
|
gc->cursor = 0; gc->tried_super = 0; |
|
u_free(gc->candidate_ids); gc->candidate_ids = NULL; gc->candidate_count = 0; |
|
|
|
uint64_t* ids = NULL; int count = 0; |
|
sqlite3* db = gc->group->instance->topo_sqlite_db; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: cycle_restart ch=%s active=%d", |
|
TGC_ID, gc->group->channel_id, gc->active_conn_count); |
|
if (!db || topo_node_sqlite_get_connected_peers(db, gc->group->channel_id, &ids, &count) != 0 || count == 0) { |
|
if (ids) { u_free(ids); ids = NULL; } |
|
gc->phase = TGC_PHASE_TWO; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: cycle_restart ch=%s no connected peers → Phase 2", TGC_ID, gc->group->channel_id); |
|
tgc_phase2_try_next(gc); |
|
return; |
|
} |
|
|
|
gc->phase = TGC_PHASE_ONE; gc->pending = count; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: cycle_restart Phase 1 launching %d connects ch=%s", |
|
TGC_ID, count, gc->group->channel_id); |
|
for (int i = 0; i < count && gc->handle_count < TGC_MAX_HANDLES; i++) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 connect to 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)ids[i], i + 1, count); |
|
node_conn_direct_open(gc->group->instance, ids[i], tgc_callback, gc, |
|
&gc->handles[gc->handle_count++], NULL); |
|
} |
|
tgc_notify_connecting(gc, ids, gc->handle_count); |
|
u_free(ids); |
|
|
|
gc->phase_timer = uasync_set_timeout(gc->group->instance->ua, |
|
TGC_DIRECT_TIMEOUT_MS * 10, gc, tgc_phase1_timeout, "tgc_phase1"); |
|
}
|
|
|