13 changed files with 345 additions and 737 deletions
@ -1,679 +1,283 @@ |
|||||||
/*
|
/* Политика CHAT-подключений. Транспортом и JOIN владеет групповая сессия.
|
||||||
* topo_group_connect.c — авто-подключение к узлам группы (бесконечный цикл до цели) |
* Сначала параллельно восстанавливаем connected-пиров, затем последовательно |
||||||
* |
* пробуем supernode/public/local. Незавершённые попытки — отменяемые запросы. */ |
||||||
* Phase 1: одновременный запуск node_conn_direct_open для пиров с connected=1. |
#include <string.h> |
||||||
* Таймаут = TGC_DIRECT_TIMEOUT_MS. Если цель достигнута → done. |
#include <limits.h> |
||||||
* |
|
||||||
* Phase 2: последовательный перебор (supernode, затем public/EIM адреса). |
|
||||||
* После каждого TIMEOUT — пауза перед следующей попыткой. |
|
||||||
* |
|
||||||
* Phase 3: последовательный перебор локальных/strict NAT адресов. |
|
||||||
* После каждого TIMEOUT — пауза перед следующей попыткой. |
|
||||||
* |
|
||||||
* После исчерпания Phase 3 → пауза → cycle_restart → Phase 1 (бесконечно). |
|
||||||
* |
|
||||||
* Цель (tgc_goal_reached): подключены хотя бы к одному суперузлу (node_type==4) |
|
||||||
* ЛИБО к 3+ не-мобильным клиентам (device_type != MOBILE, не суперузлам). |
|
||||||
* Пока цель не достигнута, успешное подключение НЕ останавливает цикл — |
|
||||||
* продолжается перебор следующих кандидатов. |
|
||||||
* |
|
||||||
* Пауза: активный режим — 1с; фоновый (Android standby) — standby_wait |
|
||||||
* (burst → сразу, sleep → до следующего burst). |
|
||||||
* При DOWN, если цель перестала выполняться — немедленный 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_connect.h" |
||||||
#include "topo_group.h" |
#include "topo_group.h" |
||||||
#include "topo_node.h" |
|
||||||
#include "topo_node_sqlite.h" |
#include "topo_node_sqlite.h" |
||||||
|
#include "topo_recovery.h" |
||||||
#include "../utun_instance.h" |
#include "../utun_instance.h" |
||||||
#include "../transport_layer/node_conn_direct.h" |
|
||||||
#include "../lib/debug_config.h" |
#include "../lib/debug_config.h" |
||||||
#include "../lib/mem.h" |
#include "../lib/mem.h" |
||||||
#include "../lib/u_async.h" |
#include "../lib/platform_compat.h" |
||||||
#include "../lib/ll_queue.h" |
|
||||||
#include "etcp.h" |
|
||||||
#include "etcp_connections.h" |
|
||||||
#include "../chat/chat_event.h" |
#include "../chat/chat_event.h" |
||||||
|
#include "etcp.h" |
||||||
#ifdef UTUN_HAVE_STANDBY |
#ifdef UTUN_HAVE_STANDBY |
||||||
#include "standby.h" |
#include "standby.h" |
||||||
#endif |
#endif |
||||||
|
|
||||||
/* ─── внутренние константы ─── */ |
#define TGC_PARALLEL 128 |
||||||
#define TGC_ID "topo_group_connect" |
#define TGC_PAUSE_TB 10000 |
||||||
#define TGC_DIRECT_TIMEOUT_MS 2000 |
|
||||||
#define TGC_PHASE_ONE 0 |
struct tgc_candidate { |
||||||
#define TGC_PHASE_TWO 1 |
uint64_t node_id, started; |
||||||
#define TGC_PHASE_THREE 2 |
struct TOPO_PEER_REQUEST* request; |
||||||
#define TGC_PHASE_DONE 3 |
uint8_t priority, tried, manual; |
||||||
#define TGC_PHASE_PAUSE 4 |
}; |
||||||
#define TGC_MAX_HANDLES 128 |
|
||||||
#define TGC_PAUSE_ACTIVE_TB 10000 /* 1s пауза в активном режиме */ |
|
||||||
#define TGC_GOAL_SUPERNODES 1 /* цель: подключены хотя бы к одному суперузлу */ |
|
||||||
#define TGC_GOAL_NONMOBILE 3 /* цель: подключены к 3+ не-мобильным клиентам (не суперузлам) */ |
|
||||||
|
|
||||||
struct TOPO_GROUP_CONNECT { |
struct TOPO_GROUP_CONNECT { |
||||||
struct TOPO_GROUP* group; |
struct TOPO_GROUP* group; |
||||||
void* phase_timer; |
struct tgc_candidate* candidates; |
||||||
void* pause_timer; /* uasync timeout handle (активный режим) */ |
size_t count; |
||||||
void* pause_wait; /* standby_wait handle (фоновый режим, Android) */ |
void *wake, *timer, *pause_timer, *pause_wait; |
||||||
uint8_t phase; |
uint8_t automatic, cycle_done, paused, reload_after_pause; |
||||||
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_step(void* arg); |
||||||
static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc); |
|
||||||
static void tgc_phase1_timeout(void* arg); |
static void tgc_notify(struct TOPO_GROUP_CONNECT* gc) { |
||||||
static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc); |
size_t length = strlen(gc->group->channel_id), count = 0; |
||||||
static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label); |
for (size_t i = 0; i < gc->count; i++) if (gc->candidates[i].request) count++; |
||||||
static int tgc_goal_reached(struct TOPO_GROUP_CONNECT* gc); |
size_t size = 1 + length + 2 + count * 8; |
||||||
|
uint8_t* data = u_malloc(size); |
||||||
static void tgc_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg); |
if (!data) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "connecting notification allocation failed"); return; } |
||||||
|
data[0] = (uint8_t)length; memcpy(data + 1, gc->group->channel_id, length); |
||||||
/* ─── нотификация GUI о списке узлов в процессе подключения ─── */ |
uint16_t n = (uint16_t)count; memcpy(data + 1 + length, &n, 2); |
||||||
|
uint8_t* out = data + 3 + length; |
||||||
/**
|
for (size_t i = 0; i < gc->count; i++) if (gc->candidates[i].request) { |
||||||
* Отправляет в GUI событие CHAT_EVT_CONNECTING_NODES — список узлов, к которым |
memcpy(out, &gc->candidates[i].node_id, 8); out += 8; |
||||||
* сейчас идёт попытка подключения. GUI по нему рисует жёлтый кружок у этих узлов. |
} |
||||||
* Пустой список (count=0) означает «сейчас ни к кому не подключаемся» — |
chat_event_post(gc->group->instance, CHAT_EVT_CONNECTING_NODES, data, (int)size); u_free(data); |
||||||
* все жёлтые кружки снимаются. |
|
||||||
*/ |
|
||||||
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(gc->group->instance, 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); |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
int topo_group_connect_active_count(struct TOPO_GROUP* group) { |
||||||
* Условие достижения цели авто-подключения |
int count = 0; |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
for (struct ll_entry* e = group && group->senders_list ? group->senders_list->head : NULL; e; e = e->next) { |
||||||
|
struct TOPO_GROUP_CONN_ITEM* peer = (struct TOPO_GROUP_CONN_ITEM*)e->data; |
||||||
/* Тип устройства пира из handshake (peer_device_type на первом UP-линке). */ |
if (topo_group_peer_ready(group, peer->node_id)) count++; |
||||||
static uint8_t tgc_peer_device_type(struct ETCP_CONN* conn) { |
} |
||||||
if (!conn || !conn->links) return CLIENT_TYPE_SERVER; |
return count; |
||||||
return conn->links->peer_device_type; |
|
||||||
} |
} |
||||||
|
|
||||||
/**
|
static int tgc_goal(struct TOPO_GROUP_CONNECT* gc) { |
||||||
* Достигнута ли цель подключения к группе: |
int super = 0, other = 0; |
||||||
* - подключены хотя бы к одному суперузлу (peers node_type==4), ЛИБО |
|
||||||
* - подключены к 3+ не-мобильным клиентам (device_type != MOBILE и не суперузел). |
|
||||||
* |
|
||||||
* Сканирует senders_list (активные BGP-пиры) с дедупом по node_id. |
|
||||||
* Состояние не хранится — пересчитывается на лету в редких точках принятия |
|
||||||
* решения (таймаут фазы, каждый успех, каждый down). |
|
||||||
*/ |
|
||||||
static int tgc_goal_reached(struct TOPO_GROUP_CONNECT* gc) { |
|
||||||
if (!gc || !gc->group || !gc->group->senders_list) return 0; |
|
||||||
struct TOPO_GROUP* group = gc->group; |
struct TOPO_GROUP* group = gc->group; |
||||||
sqlite3* db = group->instance->topo_sqlite_db; |
for (struct ll_entry* e = group->senders_list->head; e; e = e->next) { |
||||||
|
struct TOPO_GROUP_CONN_ITEM* peer = (struct TOPO_GROUP_CONN_ITEM*)e->data; |
||||||
uint64_t seen[TGC_MAX_HANDLES]; int seen_count = 0; |
if (!topo_group_peer_ready(group, peer->node_id)) continue; |
||||||
int supernode_count = 0, nonmobile_count = 0; |
if (topo_node_sqlite_get_node_type(group->instance->topo_sqlite_db, group->channel_id, peer->node_id) == 4) super++; |
||||||
|
else if (!peer->conn->links || peer->conn->links->peer_device_type != CLIENT_TYPE_MOBILE) other++; |
||||||
struct ll_entry* e = group->senders_list->head; |
|
||||||
while (e) { |
|
||||||
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; |
|
||||||
struct ETCP_CONN* conn = item ? item->conn : NULL; |
|
||||||
if (conn) { |
|
||||||
int dup = 0; |
|
||||||
for (int i = 0; i < seen_count; i++) if (seen[i] == conn->peer_node_id) { dup = 1; break; } |
|
||||||
if (!dup && seen_count < TGC_MAX_HANDLES) { |
|
||||||
seen[seen_count++] = conn->peer_node_id; |
|
||||||
int is_super = db && topo_node_sqlite_get_node_type(db, group->channel_id, conn->peer_node_id) == 4; |
|
||||||
int is_mobile = tgc_peer_device_type(conn) == CLIENT_TYPE_MOBILE; |
|
||||||
if (is_super) supernode_count++; |
|
||||||
else if (!is_mobile) nonmobile_count++; |
|
||||||
} |
|
||||||
} |
|
||||||
e = e->next; |
|
||||||
} |
} |
||||||
|
return super >= 1 || other >= 3; |
||||||
int reached = (supernode_count >= TGC_GOAL_SUPERNODES) || (nonmobile_count >= TGC_GOAL_NONMOBILE); |
|
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: goal ch=%s super=%d/%d nonmobile=%d/%d → %s", |
|
||||||
TGC_ID, group->channel_id, supernode_count, TGC_GOAL_SUPERNODES, |
|
||||||
nonmobile_count, TGC_GOAL_NONMOBILE, reached ? "reached" : "not reached"); |
|
||||||
return reached; |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
static struct tgc_candidate* tgc_add(struct TOPO_GROUP_CONNECT* gc, uint64_t id, uint8_t priority, int manual) { |
||||||
* Жизненный цикл |
for (size_t i = 0; i < gc->count; i++) if (gc->candidates[i].node_id == id) return &gc->candidates[i]; |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
struct tgc_candidate* list = u_realloc(gc->candidates, (gc->count + 1) * sizeof(*list)); |
||||||
|
if (!list) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group candidate allocation failed"); return NULL; } |
||||||
/**
|
gc->candidates = list; |
||||||
* Запуск авто-подключения к узлам CHAT-группы. |
list[gc->count] = (struct tgc_candidate){ .node_id = id, .priority = priority, .manual = manual }; |
||||||
* Если модуль уже запущен — останавливает его и стартует заново с нуля. |
return &list[gc->count++]; |
||||||
* Начинает с 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; |
static void tgc_load(struct TOPO_GROUP_CONNECT* gc) { |
||||||
sqlite3* db = group->instance->topo_sqlite_db; |
size_t retained = 0; |
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: init ch=%s db=%p", TGC_ID, group->channel_id, (void*)db); |
for (size_t i = 0; i < gc->count; i++) { |
||||||
if (!db || topo_node_sqlite_get_connected_peers(db, group->channel_id, &ids, &count) != 0 || count == 0) { |
struct tgc_candidate* c = &gc->candidates[i]; |
||||||
if (ids) { u_free(ids); ids = NULL; } |
if (c->manual && c->request) gc->candidates[retained++] = *c; |
||||||
gc->phase = TGC_PHASE_TWO; |
else topo_group_peer_close(c->request); |
||||||
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->count = retained; gc->cycle_done = 0; |
||||||
gc->phase = TGC_PHASE_ONE; |
sqlite3* db = gc->group->instance->topo_sqlite_db; |
||||||
int launched = 0; |
for (int priority = 0; priority < 4; priority++) { |
||||||
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); |
uint64_t* ids = NULL; int count = 0, rc; |
||||||
for (int i = 0; i < count && gc->handle_count < TGC_MAX_HANDLES; i++) { |
if (priority == 0) rc = topo_node_sqlite_get_connected_peers(db, gc->group->channel_id, &ids, &count); |
||||||
if (ids[i] == group->instance->node_id) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 skip self 0x%016llx", TGC_ID, (unsigned long long)ids[i]); continue; } |
else if (priority == 1) rc = topo_node_sqlite_get_supernode_peers(db, gc->group->channel_id, &ids, &count); |
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 connect to 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)ids[i], i + 1, count); |
else if (priority == 2) rc = topo_node_sqlite_get_public_peers(db, gc->group->channel_id, &ids, &count, gc->group->instance->node_id); |
||||||
node_conn_direct_open(group->instance, ids[i], tgc_callback, gc, |
else rc = topo_node_sqlite_get_local_peers(db, gc->group->channel_id, &ids, &count, gc->group->instance->node_id); |
||||||
&gc->handles[gc->handle_count++], NULL); |
if (rc < 0) DEBUG_WARN(DEBUG_CATEGORY_BGP, "cannot load group candidates group=%016llx priority=%d", |
||||||
launched++; |
(unsigned long long)gc->group->group_id, priority); |
||||||
|
else for (int i = 0; i < count; i++) |
||||||
|
if (ids[i] != gc->group->instance->node_id && !tgc_add(gc, ids[i], priority, 0)) break; |
||||||
|
u_free(ids); |
||||||
} |
} |
||||||
gc->pending = launched; |
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group connect cycle: group=%016llx candidates=%zu", |
||||||
tgc_notify_connecting(gc, ids, gc->handle_count); |
(unsigned long long)gc->group->group_id, gc->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; |
|
||||||
} |
} |
||||||
|
|
||||||
/**
|
static void tgc_cancel_pause(struct TOPO_GROUP_CONNECT* gc) { |
||||||
* Полная остановка авто-подключения: отменяет все таймеры, закрывает все |
if (gc->pause_timer) uasync_cancel_timeout(gc->group->instance->ua, gc->pause_timer); |
||||||
* открытые соединения и освобождает память. Вызывается при удалении группы |
|
||||||
* и при перезапуске (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 |
#ifdef UTUN_HAVE_STANDBY |
||||||
if (gc->pause_wait) { standby_wait_cancel(gc->pause_wait); gc->pause_wait = NULL; } |
if (gc->pause_wait) standby_wait_cancel(gc->pause_wait); |
||||||
#endif |
#endif |
||||||
for (int i = 0; i < gc->handle_count; i++) node_conn_direct_close(gc->handles[i]); |
gc->pause_timer = NULL; gc->pause_wait = NULL; gc->paused = 0; |
||||||
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); |
|
||||||
} |
} |
||||||
|
|
||||||
/**
|
static void tgc_resume(void* arg) { |
||||||
* Сколько сейчас живых соединений с пирами группы. Нужно внешней логике |
struct TOPO_GROUP_CONNECT* gc = arg; |
||||||
* (например, chat_sync) для решения, запускать ли переподключение. |
gc->pause_timer = NULL; gc->pause_wait = NULL; gc->paused = 0; |
||||||
*/ |
if (gc->reload_after_pause) tgc_load(gc); |
||||||
int topo_group_connect_active_count(struct TOPO_GROUP* group) { |
topo_group_connect_changed(gc->group); |
||||||
struct TOPO_GROUP_CONNECT* gc = group->connect; |
|
||||||
return gc ? gc->active_conn_count : 0; |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
static void tgc_pause(struct TOPO_GROUP_CONNECT* gc, int reload) { |
||||||
* ON UP / DOWN |
gc->paused = 1; gc->reload_after_pause = reload; |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
#ifdef UTUN_HAVE_STANDBY |
||||||
|
if (standby_is_enabled()) { |
||||||
/**
|
gc->pause_wait = standby_wait(gc, tgc_resume); |
||||||
* Обработка появления соединения с пиром. Увеличивает счётчик активных и |
if (gc->pause_wait) return; |
||||||
* помечает connected=1 в БД (чтобы при следующем старте узел попал в Phase 1). |
DEBUG_WARN(DEBUG_CATEGORY_BGP, "cannot wait for standby; using group retry timer"); |
||||||
* Пропускает, если пир уже подключён через другое соединение. |
|
||||||
*/ |
|
||||||
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; |
|
||||||
if (peer == group->instance->node_id) return; |
|
||||||
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; |
|
||||||
} |
} |
||||||
|
#endif |
||||||
gc->active_conn_count++; |
gc->pause_timer = uasync_set_timeout(gc->group->instance->ua, TGC_PAUSE_TB, gc, tgc_resume, "group_connect_pause"); |
||||||
sqlite3* db = group->instance->topo_sqlite_db; |
if (!gc->pause_timer) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group retry timer allocation failed"); gc->paused = 0; } |
||||||
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); |
|
||||||
} |
} |
||||||
|
|
||||||
/**
|
static int tgc_open(struct TOPO_GROUP_CONNECT* gc, struct tgc_candidate* c) { |
||||||
* Обработка обрыва соединения. Уменьшает счётчик активных и помечает |
c->tried = 1; c->started = get_time_tb(); |
||||||
* connected=0 в БД. Если живых соединений не осталось — немедленно |
int result = topo_group_peer_open(gc->group, c->node_id, &c->request); |
||||||
* перезапускает цикл поиска с Phase 1. |
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group connect attempt: group=%016llx node=%016llx priority=%u manual=%u result=%d", |
||||||
*/ |
(unsigned long long)gc->group->group_id, (unsigned long long)c->node_id, c->priority, c->manual, result); |
||||||
void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { |
if (result == 0) topo_group_connect_changed(gc->group); |
||||||
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || !conn) return; |
return result; |
||||||
uint64_t peer = conn->peer_node_id; |
|
||||||
if (peer == group->instance->node_id) return; |
|
||||||
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 (!tgc_goal_reached(gc)) { |
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN goal not reached — cycle restart ch=%s", TGC_ID, group->channel_id); |
|
||||||
tgc_cycle_restart(gc); |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
static uint64_t tgc_deadline(struct tgc_candidate* c, enum topo_peer_phase phase) { |
||||||
* Единый коллбэк для всех фаз |
return phase == TOPO_PEER_CONNECTING ? c->started + TOPO_RECOVERY_CONNECT_TIMEOUT_MS * 10ULL : |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
topo_group_peer_progress(c->request) + TOPO_RECOVERY_SYNC_TIMEOUT_MS * 10ULL; |
||||||
|
|
||||||
/**
|
|
||||||
* Единый коллбэк на результат каждой попытки подключения (успех/таймаут/обрыв). |
|
||||||
* В Phase 1 — просто считает результаты (итог подводит tgc_phase1_timeout). |
|
||||||
* В Phase 2/3 — успех проверяет цель: достигнута → done, иначе следующий кандидат; |
|
||||||
* провал закрывает handle (чтобы не оставить залипшее состояние) и ставит паузу. |
|
||||||
*/ |
|
||||||
/* Передача владения: после topo_group_new_conn (который взял свой NCD-handle)
|
|
||||||
* закрываем connect-фазу handle, чтобы владельцем conn остался один BGP-хэндл |
|
||||||
* (в senders_list), без двойного счёта. */ |
|
||||||
static void tgc_release_handle(struct TOPO_GROUP_CONNECT* gc, struct NODE_CONN_DIRECT* h) { |
|
||||||
if (!gc || !h) return; |
|
||||||
for (int i = 0; i < gc->handle_count; i++) { |
|
||||||
if (gc->handles[i] == h) { gc->handles[i] = NULL; node_conn_direct_close(h); return; } |
|
||||||
} |
|
||||||
if (gc->cur_handle == h) { gc->cur_handle = NULL; node_conn_direct_close(h); return; } |
|
||||||
node_conn_direct_close(h); |
|
||||||
} |
} |
||||||
|
|
||||||
static void tgc_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg) { |
static void tgc_timeout(void* arg) { |
||||||
struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg; |
struct TOPO_GROUP_CONNECT* gc = arg; gc->timer = NULL; tgc_step(gc); |
||||||
struct ETCP_CONN* conn = node_conn_direct_get_conn(h); |
} |
||||||
uint64_t node_id = node_conn_direct_node_id(h); |
|
||||||
|
|
||||||
int ok = (event == NCD_EVENT_UP); |
|
||||||
|
|
||||||
if (ok) { |
static void tgc_step(void* arg) { |
||||||
/* запуск BGP: узел соединён с группой (topo_group_new_conn берёт владение NCD-handle) */ |
struct TOPO_GROUP_CONNECT* gc = arg; |
||||||
if (conn) topo_group_new_conn(gc->group, conn); |
if (gc->wake) { uasync_call_soon_cancel(gc->group->instance->ua, gc->wake); gc->wake = NULL; } |
||||||
tgc_release_handle(gc, h); |
if (gc->timer) { uasync_cancel_timeout(gc->group->instance->ua, gc->timer); gc->timer = NULL; } |
||||||
} else { |
uint64_t now = get_time_tb(); |
||||||
// Каждая попытка даёт один результат. Поздние DOWN/CLOSED не должны уменьшать pending повторно.
|
int changed = 0, active = 0, failed = 0; |
||||||
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(gc->group, node_id); |
for (size_t i = 0; i < gc->count; i++) { |
||||||
if (nq) nq->handle = NULL; |
struct tgc_candidate* c = &gc->candidates[i]; |
||||||
tgc_release_handle(gc, h); |
if (!c->request) continue; |
||||||
|
enum topo_peer_phase phase = topo_group_peer_phase(c->request); |
||||||
|
if (phase == TOPO_PEER_READY || phase == TOPO_PEER_FAILED || now >= tgc_deadline(c, phase)) { |
||||||
|
if (phase != TOPO_PEER_READY) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "group connect failed: group=%016llx node=%016llx phase=%d", |
||||||
|
(unsigned long long)gc->group->group_id, (unsigned long long)c->node_id, phase); |
||||||
|
if (!c->manual && c->priority) failed = 1; |
||||||
|
} else DEBUG_INFO(DEBUG_CATEGORY_BGP, "group connect READY: group=%016llx node=%016llx", |
||||||
|
(unsigned long long)gc->group->group_id, (unsigned long long)c->node_id); |
||||||
|
struct TOPO_PEER_REQUEST* request = c->request; c->request = NULL; |
||||||
|
topo_group_peer_close(request); changed = 1; |
||||||
|
} else if (!c->manual) active++; |
||||||
} |
} |
||||||
|
if (gc->automatic && tgc_goal(gc)) { |
||||||
switch (gc->phase) { |
tgc_cancel_pause(gc); |
||||||
case TGC_PHASE_ONE: |
if (!gc->cycle_done) { |
||||||
gc->pending--; |
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group connect goal reached: group=%016llx", (unsigned long long)gc->group->group_id); |
||||||
if (ok) gc->connected_count++; |
for (size_t i = 0; i < gc->count; i++) if (!gc->candidates[i].manual && gc->candidates[i].request) { |
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 result node=0x%016llx %s pending=%d connected=%d", |
struct TOPO_PEER_REQUEST* request = gc->candidates[i].request; gc->candidates[i].request = NULL; |
||||||
TGC_ID, (unsigned long long)node_id, ok ? "OK" : "FAIL", gc->pending, gc->connected_count); |
topo_group_peer_close(request); changed = 1; |
||||||
break; |
|
||||||
case TGC_PHASE_TWO: |
|
||||||
if (ok) { |
|
||||||
if (tgc_goal_reached(gc)) { |
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — goal reached, 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 connected to 0x%016llx — goal not reached, try next", TGC_ID, (unsigned long long)node_id); |
|
||||||
tgc_phase2_try_next(gc); |
|
||||||
} |
} |
||||||
} else { |
gc->cycle_done = 1; |
||||||
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; |
} else if (gc->automatic && !gc->paused) { |
||||||
case TGC_PHASE_THREE: |
if (gc->cycle_done) { tgc_load(gc); active = 0; } |
||||||
if (ok) { |
if (failed) tgc_pause(gc, 0); |
||||||
if (tgc_goal_reached(gc)) { |
else { |
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — goal reached, done", TGC_ID, (unsigned long long)node_id); |
/* Priority 0 — parallel historical peers; all other attempts are sequential. */ |
||||||
gc->phase = TGC_PHASE_DONE; |
int available = 0; |
||||||
tgc_notify_connecting(gc, NULL, 0); |
for (size_t i = 0; i < gc->count; i++) { |
||||||
} else { |
struct tgc_candidate* c = &gc->candidates[i]; |
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — goal not reached, try next", TGC_ID, (unsigned long long)node_id); |
if (c->manual || c->tried) continue; |
||||||
tgc_phase3_try_next(gc); |
available = 1; |
||||||
|
if (active && (c->priority || active >= TGC_PARALLEL)) break; |
||||||
|
if (tgc_open(gc, c) == 0) { active++; changed = 1; } |
||||||
} |
} |
||||||
} else { |
if (!available && !active) tgc_pause(gc, 1); |
||||||
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; |
|
||||||
} |
} |
||||||
} |
uint64_t earliest = UINT64_MAX; |
||||||
|
for (size_t i = 0; i < gc->count; i++) if (gc->candidates[i].request) { |
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
uint64_t deadline = tgc_deadline(&gc->candidates[i], topo_group_peer_phase(gc->candidates[i].request)); |
||||||
* Пауза: 1s ACTIVE / 30s STANDBY |
if (deadline < earliest) earliest = deadline; |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
|
||||||
|
|
||||||
/**
|
|
||||||
* Пауза между попытками подключения. Длительность зависит от активности |
|
||||||
* приложения: 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 |
if (earliest != UINT64_MAX) { |
||||||
gc->pause_timer = uasync_set_timeout(gc->group->instance->ua, TGC_PAUSE_ACTIVE_TB, gc, cb, label); |
uint64_t delay = earliest > now ? earliest - now : 1; |
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: pause %dms (active) for ch=%s", |
gc->timer = uasync_set_timeout(gc->group->instance->ua, delay > INT_MAX ? INT_MAX : (int)delay, gc, tgc_timeout, "group_connect"); |
||||||
TGC_ID, TGC_PAUSE_ACTIVE_TB / 10, gc->group->channel_id); |
if (!gc->timer) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group connect timer allocation failed"); |
||||||
} |
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
|
||||||
* 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 (tgc_goal_reached(gc)) { |
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — goal reached (%d connected), skipping Phase 2", TGC_ID, gc->connected_count); |
|
||||||
gc->phase = TGC_PHASE_DONE; |
|
||||||
tgc_notify_connecting(gc, NULL, 0); |
|
||||||
return; |
|
||||||
} |
} |
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — goal not reached (%d connected) → Phase 2", TGC_ID, gc->connected_count); |
if (changed) tgc_notify(gc); |
||||||
gc->phase = TGC_PHASE_TWO; |
|
||||||
tgc_phase2_try_next(gc); |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
static void tgc_wake(void* arg) { |
||||||
* Phase 2 |
struct TOPO_GROUP_CONNECT* gc = arg; gc->wake = NULL; tgc_step(gc); |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
|
||||||
|
|
||||||
/**
|
|
||||||
* Последовательный перебор узлов с публичными/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++]; |
|
||||||
if (nid == gc->group->instance->node_id) continue; |
|
||||||
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; |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
void topo_group_connect_changed(struct TOPO_GROUP* group) { |
||||||
* Phase 3 — локальные/strict NAT адреса |
struct TOPO_GROUP_CONNECT* gc = group ? group->connect : NULL; |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
if (!gc || group->stopping || gc->wake) return; |
||||||
|
gc->wake = uasync_call_soon(group->instance->ua, gc, tgc_wake); |
||||||
/**
|
if (!gc->wake) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group connect wake allocation failed"); |
||||||
* Последовательный перебор узлов с локальными/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++]; |
|
||||||
if (nid == gc->group->instance->node_id) continue; |
|
||||||
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"); |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
void topo_group_connect_destroy(struct TOPO_GROUP* group) { |
||||||
* Полный перезапуск цикла с Phase 1 (без destroy/init) |
struct TOPO_GROUP_CONNECT* gc = group ? group->connect : NULL; |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
if (!gc) return; |
||||||
|
group->connect = NULL; |
||||||
/**
|
if (gc->wake) uasync_call_soon_cancel(group->instance->ua, gc->wake); |
||||||
* Полный перезапуск цикла с Phase 1 без уничтожения модуля. Закрывает все |
if (gc->timer) uasync_cancel_timeout(group->instance->ua, gc->timer); |
||||||
* открытые соединения, сбрасывает состояние и заново запускает параллельные |
tgc_cancel_pause(gc); |
||||||
* попытки ко всем connected-пирам из БД. Вызывается при обрыве всех соединений |
for (size_t i = 0; i < gc->count; i++) topo_group_peer_close(gc->candidates[i].request); |
||||||
* и после исчерпания Phase 3. |
u_free(gc->candidates); u_free(gc); |
||||||
*/ |
} |
||||||
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; |
int topo_group_connect_init(struct TOPO_GROUP* group) { |
||||||
int launched = 0; |
if (!group || group->stopping || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0]) { |
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: cycle_restart Phase 1 launching %d connects ch=%s", |
DEBUG_WARN(DEBUG_CATEGORY_BGP, "cannot start group connect without a CHAT group"); return -1; |
||||||
TGC_ID, count, gc->group->channel_id); |
|
||||||
for (int i = 0; i < count && gc->handle_count < TGC_MAX_HANDLES; i++) { |
|
||||||
if (ids[i] == gc->group->instance->node_id) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 skip self 0x%016llx", TGC_ID, (unsigned long long)ids[i]); continue; } |
|
||||||
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); |
|
||||||
launched++; |
|
||||||
} |
} |
||||||
gc->pending = launched; |
topo_group_connect_destroy(group); |
||||||
tgc_notify_connecting(gc, ids, gc->handle_count); |
struct TOPO_GROUP_CONNECT* gc = u_calloc(1, sizeof(*gc)); |
||||||
u_free(ids); |
if (!gc) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group connect allocation failed"); return -1; } |
||||||
|
gc->group = group; gc->automatic = 1; group->connect = gc; |
||||||
gc->phase_timer = uasync_set_timeout(gc->group->instance->ua, |
tgc_load(gc); topo_group_connect_changed(group); return 0; |
||||||
TGC_DIRECT_TIMEOUT_MS * 10, gc, tgc_phase1_timeout, "tgc_phase1"); |
|
||||||
} |
} |
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
void topo_group_connect_restart(struct TOPO_GROUP* group) { |
||||||
* Однократное подключение к конкретному узлу (ручной "Connect" из GUI) |
if (!group) return; |
||||||
* ══════════════════════════════════════════════════════════════════════ */ |
if (topo_group_connect_active_count(group)) return; |
||||||
|
topo_group_connect_init(group); |
||||||
struct tgc_once_ctx { |
} |
||||||
struct TOPO_GROUP* group; |
|
||||||
uint64_t node_id; |
|
||||||
struct NODE_CONN_DIRECT* h; |
|
||||||
}; |
|
||||||
|
|
||||||
static void tgc_once_cb(struct NODE_CONN_DIRECT* h, enum ncd_event ev, void* arg) { |
void topo_group_connect_on_up(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { |
||||||
struct tgc_once_ctx* c = (struct tgc_once_ctx*)arg; |
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !conn) return; |
||||||
struct ETCP_CONN* conn = node_conn_direct_get_conn(h); |
if (group->instance->topo_sqlite_db && topo_group_peer_ready(group, conn->peer_node_id)) |
||||||
|
topo_node_sqlite_set_connected(group->instance->topo_sqlite_db, group->channel_id, conn->peer_node_id, 1); |
||||||
|
topo_group_connect_changed(group); |
||||||
|
} |
||||||
|
|
||||||
if (ev == NCD_EVENT_UP && conn) { |
void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { |
||||||
node_conn_direct_set_callback(h, NULL, NULL); |
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !conn) return; |
||||||
topo_group_new_conn(c->group, conn); |
if (group->instance->topo_sqlite_db) |
||||||
node_conn_direct_close(h); /* владение перешло к topo_group_new_conn */ |
topo_node_sqlite_set_connected(group->instance->topo_sqlite_db, group->channel_id, conn->peer_node_id, 0); |
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: once-connect UP node=0x%016llx ch=%s", |
topo_group_connect_changed(group); |
||||||
TGC_ID, (unsigned long long)c->node_id, c->group->channel_id); |
|
||||||
} else if (ev == NCD_EVENT_TIMEOUT) { |
|
||||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "%s: once-connect TIMEOUT node=0x%016llx ch=%s", |
|
||||||
TGC_ID, (unsigned long long)c->node_id, c->group->channel_id); |
|
||||||
node_conn_direct_close(h); |
|
||||||
} else if (ev == NCD_EVENT_DOWN || ev == NCD_EVENT_CLOSED) { |
|
||||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "%s: once-connect DOWN node=0x%016llx ch=%s", |
|
||||||
TGC_ID, (unsigned long long)c->node_id, c->group->channel_id); |
|
||||||
node_conn_direct_close(h); |
|
||||||
} |
|
||||||
u_free(c); |
|
||||||
} |
} |
||||||
|
|
||||||
int topo_group_connect_node_once(struct TOPO_GROUP* group, uint64_t node_id) { |
int topo_group_connect_node_once(struct TOPO_GROUP* group, uint64_t node_id) { |
||||||
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !node_id) return -1; |
if (!group || group->stopping || group->group_type != TOPO_GROUP_TYPE_CHAT || !node_id || node_id == group->instance->node_id) { |
||||||
if (node_id == group->instance->node_id) return -1; |
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid manual group connect"); return -1; |
||||||
|
|
||||||
struct ETCP_CONN* ex = instance_find_conn(group->instance, node_id); |
|
||||||
if (ex && ex->links_up && ex->initialized) { |
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: once-connect node=0x%016llx already connected — add to group ch=%s", |
|
||||||
TGC_ID, (unsigned long long)node_id, group->channel_id); |
|
||||||
topo_group_new_conn(group, ex); |
|
||||||
return 0; |
|
||||||
} |
} |
||||||
|
if (!group->connect) { |
||||||
struct tgc_once_ctx* c = u_calloc(1, sizeof(*c)); |
group->connect = u_calloc(1, sizeof(*group->connect)); |
||||||
|
if (!group->connect) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "manual group connect allocation failed"); return -1; } |
||||||
|
group->connect->group = group; |
||||||
|
} |
||||||
|
struct TOPO_GROUP_CONNECT* gc = group->connect; |
||||||
|
struct tgc_candidate* c = tgc_add(gc, node_id, 0, 1); |
||||||
if (!c) return -1; |
if (!c) return -1; |
||||||
c->group = group; c->node_id = node_id; |
c->manual = 1; |
||||||
|
if (c->request) return 0; |
||||||
int r = node_conn_direct_open(group->instance, node_id, tgc_once_cb, c, &c->h, NULL); |
int result = tgc_open(gc, c); tgc_notify(gc); return result; |
||||||
if (r == NCD_ERR || !c->h) { u_free(c); return -1; } |
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: once-connect node=0x%016llx ch=%s started (rc=%d)", |
|
||||||
TGC_ID, (unsigned long long)node_id, group->channel_id, r); |
|
||||||
return 0; |
|
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue