/* * 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"); }