Files
utun2/src/routing_layer/topo_group.c
T

1987 lines
109 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file topo_group.c
* @brief BGP-like обмен топологией узлов (группы)
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <openssl/sha.h>
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/memory_pool.h"
#include "utun_instance.h"
#include "etcp_api.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "etcp_debug.h"
#include "config_parser.h"
#include "topo_node.h"
#include "topo_node_sqlite.h"
#include "route_lib.h"
#include "topo_group.h"
#include "nat_detection.h"
#include "route_connectivity.h"
#include "control_server.h"
#include "conn_mgr.h"
#include "../transport_layer/node_conn_direct.h"
#include "topo_group_connect.h"
#include "topo_recovery.h"
#include "broadcast.h"
#include "multicast.h"
#include "../radio/radio.h"
#include "../stream/stream.h"
#include "../chat/chat_setting.h"
#define TOPO_GROUP_UTUN 0x8000000000000000ULL
// ============================================================================
// Вспомогательные функции
// ============================================================================
struct TOPO_PEER_REQUEST {
struct TOPO_GROUP_CONN_ITEM* peer;
struct TOPO_PEER_REQUEST* next;
};
static int topo_group_begin_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, int retain);
static int topo_group_peer_allowed(struct TOPO_GROUP* group, uint64_t node_id);
static void topo_tx_clear(struct TOPO_GROUP_CONN_ITEM* peer);
static int topo_tx_control(struct TOPO_GROUP_CONN_ITEM* peer, const void* data, size_t size);
static int topo_tx_node(struct TOPO_GROUP_CONN_ITEM* peer, uint64_t node_id);
static int topo_tx_table(struct TOPO_GROUP_CONN_ITEM* peer);
static int topo_control_wait(struct ETCP_CONN* conn, const void* data, size_t size);
static int topo_group_routes_changed(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, int event);
static void topo_group_prune_paths(struct TOPO_GROUP_CONN_ITEM* peer, int all);
static void topo_exchange_watch(void* arg);
static void topo_tx_fail(struct TOPO_GROUP_CONN_ITEM* peer);
static void topo_group_destroy(struct TOPO_GROUP* group);
static void topo_exchange_cancel(struct TOPO_GROUP_CONN_ITEM* peer) {
if (peer->exchange_timer) uasync_cancel_timeout(peer->group->instance->ua, peer->exchange_timer);
peer->exchange_timer = NULL;
}
static int topo_exchange_arm(struct TOPO_GROUP_CONN_ITEM* peer) {
if (!peer->exchange_timer)
peer->exchange_timer = uasync_set_timeout(peer->group->instance->ua, 1000, peer, topo_exchange_watch, "bgp_exchange");
if (peer->exchange_timer) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot schedule exchange watchdog group=%016llx peer=%016llx",
(unsigned long long)peer->group->group_id, (unsigned long long)peer->node_id);
return -1;
}
struct topo_advert {
struct ll_entry ll;
uint64_t node_id;
uint8_t digest[SHA256_DIGEST_LENGTH];
};
/* Отправленные анонсы относятся только к текущему обмену с этим соседом. */
static void topo_adverts_clear(struct TOPO_GROUP_CONN_ITEM* peer) {
if (!peer->advertised) return;
struct ll_entry* e;
while ((e = queue_data_get(peer->advertised))) queue_entry_free(e);
queue_free(peer->advertised); peer->advertised = NULL;
}
static struct TOPO_GROUP_CONN_ITEM* topo_group_peer(const struct TOPO_GROUP* group, uint64_t peer_id) {
for (struct ll_entry* e = group && group->senders_list ? group->senders_list->head : NULL; e; e = e->next) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
if (item->node_id == peer_id) return item;
}
return NULL;
}
int topo_group_peer_ready(const struct TOPO_GROUP* group, uint64_t peer_id) {
const struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, peer_id);
return peer && peer->conn && peer->conn->links_up && !peer->conn->close_requested && !peer->tx_failed && !peer->transport_down &&
peer->accepted && peer->table_received && peer->table_sent;
}
static void topo_group_notify_ready(struct TOPO_GROUP_CONN_ITEM* peer, int ready) {
if (peer->ready_notified == ready) return;
peer->ready_notified = ready;
nat_detection_topology_changed(peer->group->instance->nat_det);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP peer %s: group=%016llx peer=%016llx local_epoch=%016llx remote_epoch=%016llx "
"links_up=%u transport_down=%u accepted=%u table_sent=%u table_received=%u send_failed=%u",
ready ? "READY (group exchange complete)" : "NOT READY (group session unavailable)",
(unsigned long long)peer->group->group_id, (unsigned long long)peer->node_id,
(unsigned long long)peer->local_epoch, (unsigned long long)peer->peer_epoch,
peer->conn ? peer->conn->links_up : 0, peer->transport_down, peer->accepted,
peer->table_sent, peer->table_received, peer->tx_failed);
for (struct topo_peer_ready_cbk* cb = peer->group->peer_ready_cbks; cb; cb = cb->next)
cb->fn(peer->group, peer->node_id, ready, cb->arg);
}
static void topo_group_peer_free(struct TOPO_GROUP_CONN_ITEM* peer) {
topo_group_notify_ready(peer, 0);
topo_exchange_cancel(peer);
topo_tx_clear(peer);
topo_adverts_clear(peer);
while (peer->requests) {
struct TOPO_PEER_REQUEST* request = peer->requests;
peer->requests = request->next; request->peer = NULL; request->next = NULL;
}
if (peer->handle) node_conn_direct_close(peer->handle);
topo_recovery_changed(peer->group);
topo_group_connect_changed(peer->group);
}
static void topo_group_pending_remove(struct TOPO_GROUP_CONN_ITEM* peer) {
struct ll_queue* queue = peer->group->senders_list;
for (struct ll_entry* e = queue->head; e; e = e->next) {
if ((void*)e->data != peer) continue;
queue_remove_data(queue, e); topo_group_peer_free(peer); queue_entry_free(e); return;
}
}
static void topo_group_peer_event(struct NODE_CONN_DIRECT* handle, enum ncd_event event, void* arg) {
struct TOPO_GROUP_CONN_ITEM* peer = arg;
if (event == NCD_EVENT_UP) {
struct ETCP_CONN* conn = node_conn_direct_get_conn(handle);
if (conn && topo_group_begin_conn(peer->group, conn, 0) == 0) return;
DEBUG_WARN(DEBUG_CATEGORY_BGP, "group join failed peer=%016llx group=%016llx",
(unsigned long long)peer->node_id, (unsigned long long)peer->group->group_id);
}
if (event == NCD_EVENT_TIMEOUT)
DEBUG_WARN(DEBUG_CATEGORY_BGP, "group peer transport timeout peer=%016llx group=%016llx",
(unsigned long long)peer->node_id, (unsigned long long)peer->group->group_id);
if (peer->conn) topo_group_remove_conn(peer->group, peer->conn, TOPO_REMOVE_TRANSPORT_DOWN);
else if ((event == NCD_EVENT_DOWN || event == NCD_EVENT_TIMEOUT) && peer->requests && node_conn_direct_get_conn(handle)) {
peer->transport_down = 1; peer->retained = 0; topo_recovery_changed(peer->group);
topo_group_connect_changed(peer->group);
} else topo_group_pending_remove(peer);
}
int topo_group_peer_open(struct TOPO_GROUP* group, uint64_t node_id, struct TOPO_PEER_REQUEST** out) {
if (out) *out = NULL;
if (!group || !group->instance || !out || !node_id || node_id == group->instance->node_id ||
!group->senders_list || group->stopping || !topo_group_peer_allowed(group, node_id)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "cannot request group peer=%016llx: invalid arguments, stopped group or unknown member",
(unsigned long long)node_id); return -1;
}
struct TOPO_PEER_REQUEST* request = u_calloc(1, sizeof(*request));
if (!request) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group request allocation failed"); return -1; }
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, node_id);
if (!peer) {
struct ll_entry* e = queue_entry_new(sizeof(*peer));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group peer allocation failed"); u_free(request); return -1; }
peer = (struct TOPO_GROUP_CONN_ITEM*)e->data;
peer->group = group; peer->node_id = node_id; peer->progress = get_time_tb();
if (node_conn_direct_open(group->instance, node_id, topo_group_peer_event, peer, &peer->handle, NULL) == NCD_ERR) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "group NCD open failed peer=%016llx group=%016llx",
(unsigned long long)node_id, (unsigned long long)group->group_id);
queue_entry_free(e); u_free(request); return -1;
}
queue_data_put(group->senders_list, e);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group peer CONNECTING peer=%016llx group=%016llx",
(unsigned long long)node_id, (unsigned long long)group->group_id);
}
request->peer = peer; request->next = peer->requests; peer->requests = request; *out = request;
return 0;
}
enum topo_peer_phase topo_group_peer_phase(const struct TOPO_PEER_REQUEST* request) {
struct TOPO_GROUP_CONN_ITEM* peer = request ? request->peer : NULL;
if (!peer || peer->tx_failed || peer->transport_down) return TOPO_PEER_FAILED;
if (!peer->conn || !peer->conn->links_up) return TOPO_PEER_CONNECTING;
return topo_group_peer_ready(peer->group, peer->node_id) ? TOPO_PEER_READY : TOPO_PEER_SYNCING;
}
struct ETCP_CONN* topo_group_peer_conn(const struct TOPO_PEER_REQUEST* request) {
return request && request->peer ? node_conn_direct_get_conn(request->peer->handle) : NULL;
}
void topo_group_peer_leave(struct TOPO_GROUP* group, uint64_t node_id) {
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, node_id);
if (!peer) return;
if (peer->conn) topo_group_leave_peer(group, peer->handle);
else topo_group_pending_remove(peer);
}
uint64_t topo_group_peer_progress(const struct TOPO_PEER_REQUEST* request) {
return request && request->peer ? request->peer->progress : 0;
}
void topo_group_peer_close(struct TOPO_PEER_REQUEST* request) {
if (!request) return;
struct TOPO_GROUP_CONN_ITEM* peer = request->peer;
if (peer) {
struct TOPO_PEER_REQUEST** p = &peer->requests;
while (*p && *p != request) p = &(*p)->next;
if (*p) *p = request->next;
if (!peer->requests && !peer->retained) {
if (peer->conn) topo_group_leave_peer(peer->group, peer->handle);
else topo_group_pending_remove(peer);
}
}
u_free(request);
}
static void topo_group_peer_progressed(struct TOPO_GROUP* group, uint64_t node_id) {
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, node_id);
if (peer) peer->progress = get_time_tb();
topo_recovery_changed(group);
topo_group_connect_changed(group);
}
static void topo_group_log_exchange(struct TOPO_GROUP* group, struct TOPO_GROUP_CONN_ITEM* peer) {
if (topo_group_peer_ready(group, peer->node_id)) {
topo_exchange_cancel(peer);
peer->retained = 1; topo_group_connect_on_up(group, peer->conn);
}
topo_group_notify_ready(peer, topo_group_peer_ready(group, peer->node_id));
topo_group_peer_progressed(group, peer->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group exchange: group=%016llx peer=%016llx exchange=%016llx sent=%u received=%u ready=%d",
(unsigned long long)group->group_id, (unsigned long long)peer->conn->peer_node_id,
(unsigned long long)peer->local_epoch, peer->table_sent, peer->table_received,
topo_group_peer_ready(group, peer->conn->peer_node_id));
conn_mgr_routes_changed(group->conn_mgr);
}
static struct TOPOMSG_HEADER topo_header(struct TOPO_GROUP_CONN_ITEM* peer, uint8_t cmd) {
return (struct TOPOMSG_HEADER){ .cmd = ETCP_ID_TOPO_ENTRY, .subcmd = cmd, .group_id = peer->group->group_id,
.src_epoch = peer->local_epoch, .dst_epoch = peer->peer_epoch };
}
static int topo_emit_control(struct ETCP_CONN* conn, const void* data, size_t size) {
struct ll_entry* e = ll_alloc_lldgram(size);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group control allocation failed size=%zu", size); return -1; }
memcpy(e->dgram, data, size); e->len = size;
if (etcp_send(conn, e) == 0) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group control send failed peer=%016llx cmd=%u",
(unsigned long long)conn->peer_node_id, ((const uint8_t*)data)[1]);
queue_dgram_free(e); queue_entry_free(e); return -1;
}
/* Сообщения сессии проходят одну FIFO. Stateless RESYNC/REJECT также ждут
* пустой транспортной очереди, но не создают групповое участие. */
static int topo_send_control(struct ETCP_CONN* conn, const void* data, size_t size) {
if (size >= sizeof(struct TOPOMSG_HEADER)) {
const struct TOPOMSG_HEADER* h = data;
struct TOPO_GROUP* group = topo_groups_find(conn->instance->topo_groups, h->group_id);
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (peer && peer->conn == conn && peer->local_epoch == h->src_epoch) return topo_tx_control(peer, data, size);
}
return topo_control_wait(conn, data, size);
}
static int topo_group_reset_exchange(struct TOPO_GROUP_CONN_ITEM* peer) {
topo_tx_clear(peer); peer->tx_failed = 0; peer->transport_down = 0;
topo_adverts_clear(peer);
peer->accepted = 0; peer->table_received = 0; peer->table_sent = 0; peer->peer_epoch = 0;
topo_group_notify_ready(peer, 0);
uint64_t epoch;
if (random_bytes((uint8_t*)&epoch, sizeof(epoch)) != 0 || !epoch) {
peer->local_epoch = 0;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot generate group epoch group=%016llx", (unsigned long long)peer->group->group_id);
return -1;
}
peer->local_epoch = epoch;
topo_group_peer_progressed(peer->group, peer->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group session started: group=%016llx peer=%016llx local=%016llx",
(unsigned long long)peer->group->group_id, (unsigned long long)peer->node_id, (unsigned long long)epoch);
return topo_exchange_arm(peer);
}
static void topo_group_send_table_request(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (!peer || !peer->accepted) return;
struct TOPOMSG_HEADER msg = topo_header(peer, TOPO_SUBCMD_REQUEST_TABLE);
topo_send_control(conn, &msg, sizeof(msg));
}
static void topo_group_send_join_group(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (!peer || peer->conn != conn || !peer->local_epoch) return;
struct TOPOMSG_JOIN_GROUP msg = { .h = topo_header(peer, TOPO_SUBCMD_JOIN_GROUP), .group_type = group->group_type };
msg.h.dst_epoch = 0;
topo_send_control(conn, &msg, sizeof(msg));
}
static void topo_exchange_watch(void* arg) {
struct TOPO_GROUP_CONN_ITEM* peer = arg;
peer->exchange_timer = NULL;
if (!peer->conn || !peer->conn->links_up || peer->conn->close_requested || peer->transport_down || peer->group->stopping) return;
if (topo_group_peer_ready(peer->group, peer->node_id)) return;
uint64_t idle = get_time_tb() - peer->progress;
if (idle >= TOPO_GROUP_SYNC_TIMEOUT_MS * 10ULL) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "exchange stalled: group=%016llx peer=%016llx epoch=%016llx idle_ms=%llu sent=%u received=%u failed=%u",
(unsigned long long)peer->group->group_id, (unsigned long long)peer->node_id,
(unsigned long long)peer->local_epoch, (unsigned long long)(idle / 10),
peer->table_sent, peer->table_received, peer->tx_failed);
topo_group_prune_paths(peer, 1);
if (topo_group_reset_exchange(peer) == 0) topo_group_send_join_group(peer->group, peer->conn);
} else topo_exchange_arm(peer);
}
static void topo_group_send_resync(struct ETCP_CONN* conn) {
struct TOPOMSG_RESYNC msg = { .cmd = ETCP_ID_TOPO_ENTRY, .subcmd = TOPO_SUBCMD_RESYNC };
topo_send_control(conn, &msg, sizeof(msg));
}
static int topo_group_send_table_complete(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (!peer || !peer->accepted) return -1;
struct TOPOMSG_HEADER msg = topo_header(peer, TOPO_SUBCMD_TABLE_COMPLETE);
return topo_emit_control(conn, &msg, sizeof(msg));
}
static void topo_group_reject(struct ETCP_CONN* conn, const struct TOPOMSG_HEADER* h, enum topo_join_reject reason) {
struct TOPOMSG_JOIN_GROUP msg = { .h = { .cmd = ETCP_ID_TOPO_ENTRY, .subcmd = TOPO_SUBCMD_JOIN_REJECT,
.group_id = h->group_id, .dst_epoch = h->src_epoch }, .group_type = reason };
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group JOIN rejected: group=%016llx peer=%016llx requested=%016llx reason=%u",
(unsigned long long)h->group_id, (unsigned long long)conn->peer_node_id, (unsigned long long)h->src_epoch, reason);
topo_send_control(conn, &msg, sizeof(msg));
}
static int topo_group_add_to_senders(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
static bool topo_group_should_send_to(const struct TOPO_GROUP_NODE* nq, uint64_t target_id);
static void topo_group_handle_request_table(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
static void topo_group_handle_join_group(struct TOPO_GROUP* group, struct ETCP_CONN* conn, const struct TOPOMSG_JOIN_GROUP* msg);
static void topo_group_handle_resync(struct UTUN_INSTANCE* instance, struct ETCP_CONN* conn);
/* Краткий DEBUG-дамп принятого NODEINFO (id/ver/счётчики/hops). */
static void nodeinfo_dump_log(const uint8_t* data, size_t len) {
if (!data || len < sizeof(struct TOPOMSG_NODEINFO_PKT)) return;
struct TOPOMSG_NODEINFO_PKT* pkt = (struct TOPOMSG_NODEINFO_PKT*)data;
struct TOPOMSG_NODE* ni = &pkt->node;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO nid=%016llx ver=%u v4s=%u v4a=%u hops=%u",
(unsigned long long)ni->node_id, ni->ver,
ni->local_v4_sockets, ni->local_v4_addrs, ni->hop_count);
}
/* Читаемое имя sub-команды BGP для логов. */
static const char* group_subcmd_name(uint8_t subcmd) {
switch (subcmd) {
case TOPO_SUBCMD_NODEINFO: return "NODEINFO";
case TOPO_SUBCMD_REQUEST_TABLE: return "REQ_TABLE";
case TOPO_SUBCMD_WITHDRAW: return "WITHDRAW";
case TOPO_SUBCMD_TABLE_COMPLETE: return "TABLE_COMPLETE";
case TOPO_SUBCMD_ERR_GROUP_MISMATCH: return "ERR_GROUP_MISMATCH";
case TOPO_SUBCMD_JOIN_GROUP: return "JOIN_GROUP";
case TOPO_SUBCMD_JOIN_ACCEPT: return "JOIN_ACCEPT";
case TOPO_SUBCMD_JOIN_REJECT: return "JOIN_REJECT";
case TOPO_SUBCMD_RESYNC: return "RESYNC";
case TOPO_SUBCMD_LEAVE_GROUP: return "LEAVE_GROUP";
default: return "?";
}
}
// ============================================================================
// Broadcast / Withdraw
// ============================================================================
/* Пересчитать наш анонс назначения каждому соседу при освобождении его очереди. */
static void topo_group_publish_route(struct TOPO_GROUP* group, uint64_t node_id) {
if (!group) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group is NULL"); return; }
for (struct ll_entry* e = 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;
if (peer->conn && peer->accepted && !peer->transport_down) topo_tx_node(peer, node_id);
}
}
// ============================================================================
// Приём пакетов
// ============================================================================
static int topo_configured(struct UTUN_INSTANCE* instance, uint64_t node_id) {
for (struct CONFIG_CONN_HANDLE* owner = instance->config_conn_handles; owner; owner = owner->next)
if (owner->node_id == node_id) return 1;
return 0;
}
static int topo_group_has_sender(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
for (struct ll_entry* e = group->senders_list ? group->senders_list->head : NULL; e; e = e->next)
if (((struct TOPO_GROUP_CONN_ITEM*)e->data)->conn == conn) return 1;
return 0;
}
/* Транспорт не даёт членства: CHAT сверяет локальную таблицу участников. */
static int topo_group_peer_allowed(struct TOPO_GROUP* group, uint64_t node_id) {
if (group->group_type != TOPO_GROUP_TYPE_CHAT) return 1;
sqlite3* db = group->instance->topo_sqlite_db;
return db && group->channel_id[0] && topo_node_sqlite_member_in_channel(db, group->channel_id, node_id);
}
/* Приёмник BGP-пакетов от ETCP: диспетчер по subcmd (NODEINFO/WITHDRAW/...). */
static void topo_group_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry) {
if (!entry) return;
if (!from_conn || !entry->dgram || entry->len < 2) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid BGP packet length=%u", entry->len); goto done;
}
struct UTUN_INSTANCE* instance = from_conn->instance;
if (!instance || !instance->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid instance/topo_groups"); goto done; }
const uint8_t* data = entry->dgram;
uint8_t subcmd = data[1];
if (data[0] != ETCP_ID_TOPO_ENTRY) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid BGP command=%u", data[0]); goto done; }
if (subcmd == TOPO_SUBCMD_RESYNC && entry->len == sizeof(struct TOPOMSG_RESYNC)) {
topo_group_handle_resync(instance, from_conn); goto done;
}
size_t size = sizeof(struct TOPOMSG_HEADER);
if (subcmd == TOPO_SUBCMD_JOIN_GROUP || subcmd == TOPO_SUBCMD_JOIN_ACCEPT || subcmd == TOPO_SUBCMD_JOIN_REJECT)
size = sizeof(struct TOPOMSG_JOIN_GROUP);
else if (subcmd == TOPO_SUBCMD_NODEINFO) size = sizeof(struct TOPOMSG_NODEINFO_PKT);
else if (subcmd == TOPO_SUBCMD_WITHDRAW) size = sizeof(struct TOPOMSG_WITHDRAW_PKT);
else if (subcmd == TOPO_SUBCMD_ERR_GROUP_MISMATCH) size = sizeof(struct TOPOMSG_ERR_GROUP_MISMATCH);
else if (subcmd != TOPO_SUBCMD_REQUEST_TABLE && subcmd != TOPO_SUBCMD_TABLE_COMPLETE && subcmd != TOPO_SUBCMD_LEAVE_GROUP) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "unknown BGP command=%u", subcmd); goto done;
}
if (entry->len < size || (subcmd != TOPO_SUBCMD_NODEINFO && entry->len != size)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid BGP length=%u cmd=%u expected=%zu", entry->len, subcmd, size); goto done;
}
const struct TOPOMSG_HEADER* h = (const struct TOPOMSG_HEADER*)data;
struct TOPO_GROUP* group = topo_groups_find(instance->topo_groups, h->group_id);
if (subcmd == TOPO_SUBCMD_JOIN_GROUP) {
if (!h->src_epoch || h->dst_epoch) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid JOIN epochs"); goto done; }
if (!group || group->stopping) topo_group_reject(from_conn, h, TOPO_JOIN_NO_GROUP);
else if (!topo_group_peer_allowed(group, from_conn->peer_node_id)) topo_group_reject(from_conn, h, TOPO_JOIN_NOT_MEMBER);
else if (((const struct TOPOMSG_JOIN_GROUP*)data)->group_type != group->group_type)
topo_group_reject(from_conn, h, TOPO_JOIN_WRONG_TYPE);
else topo_group_handle_join_group(group, from_conn, (const struct TOPOMSG_JOIN_GROUP*)data);
goto done;
}
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, from_conn->peer_node_id);
/* До получения JOIN ответа отменяем только ещё не согласованное предложение
* с тем же src_epoch. У READY-сессии LEAVE всегда проверяет оба поколения. */
if (subcmd == TOPO_SUBCMD_LEAVE_GROUP && peer && peer->conn == from_conn && !peer->accepted &&
!h->dst_epoch && h->src_epoch && h->src_epoch == peer->peer_epoch) {
topo_group_remove_conn(group, from_conn, TOPO_REMOVE_REMOTE_LEAVE); goto done;
}
if (!peer || peer->conn != from_conn || !h->dst_epoch || h->dst_epoch != peer->local_epoch) goto stale;
if (subcmd == TOPO_SUBCMD_JOIN_REJECT) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "group JOIN refused: group=%016llx peer=%016llx local=%016llx reason=%u",
(unsigned long long)h->group_id, (unsigned long long)peer->node_id, (unsigned long long)peer->local_epoch,
((const struct TOPOMSG_JOIN_GROUP*)data)->group_type);
topo_group_remove_conn(group, from_conn, TOPO_REMOVE_REJECTED); goto done;
}
if (!h->src_epoch || (peer->peer_epoch && peer->peer_epoch != h->src_epoch)) goto stale;
if (!topo_group_peer_allowed(group, from_conn->peer_node_id)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "group peer no longer a member group=%016llx peer=%016llx",
(unsigned long long)h->group_id, (unsigned long long)peer->node_id); goto done;
}
if (subcmd == TOPO_SUBCMD_JOIN_ACCEPT) {
if (((const struct TOPOMSG_JOIN_GROUP*)data)->group_type != group->group_type) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "JOIN_ACCEPT type mismatch"); goto done;
}
if (!peer->accepted) {
peer->peer_epoch = h->src_epoch; peer->accepted = 1;
topo_group_peer_progressed(group, peer->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group JOIN accepted: group=%016llx peer=%016llx local=%016llx remote=%016llx",
(unsigned long long)h->group_id, (unsigned long long)peer->node_id,
(unsigned long long)peer->local_epoch, (unsigned long long)peer->peer_epoch);
topo_group_send_table_request(group, from_conn);
}
goto done;
}
if (subcmd == TOPO_SUBCMD_LEAVE_GROUP && peer->peer_epoch) {
topo_group_remove_conn(group, from_conn, TOPO_REMOVE_REMOTE_LEAVE); goto done;
}
if (!peer->accepted || !peer->peer_epoch) goto stale;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "BGP recv %s peer=%016llx group=%016llx local=%016llx remote=%016llx len=%u",
group_subcmd_name(subcmd), (unsigned long long)peer->node_id, (unsigned long long)h->group_id,
(unsigned long long)peer->local_epoch, (unsigned long long)peer->peer_epoch, entry->len);
if (peer->tx_failed) goto done;
if (subcmd == TOPO_SUBCMD_NODEINFO) {
nodeinfo_dump_log(data, entry->len);
if (topo_group_process_nodeinfo(group, from_conn, data, entry->len) < 0) topo_tx_fail(peer);
}
else if (subcmd == TOPO_SUBCMD_WITHDRAW) topo_group_process_withdraw(group, from_conn, data, entry->len);
else if (subcmd == TOPO_SUBCMD_REQUEST_TABLE) topo_group_handle_request_table(group, from_conn);
else if (subcmd == TOPO_SUBCMD_TABLE_COMPLETE && !peer->table_received) {
topo_group_prune_paths(peer, 0);
peer->table_received = 1; topo_group_log_exchange(group, peer);
} else if (subcmd == TOPO_SUBCMD_ERR_GROUP_MISMATCH) {
const struct TOPOMSG_ERR_GROUP_MISMATCH* err = (const struct TOPOMSG_ERR_GROUP_MISMATCH*)data;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group mismatch peer=%016llx expected=%u received=%u",
(unsigned long long)peer->node_id, err->expected_type, err->received_flags);
}
goto done;
stale:
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ignore unmatched group message cmd=%u group=%016llx peer=%016llx src=%016llx dst=%016llx",
subcmd, (unsigned long long)h->group_id, (unsigned long long)from_conn->peer_node_id,
(unsigned long long)h->src_epoch, (unsigned long long)h->dst_epoch);
done:
queue_dgram_free(entry); queue_entry_free(entry);
}
// ============================================================================
// Init / Destroy / New / Remove conn
// ============================================================================
/* Callback статуса ETCP-соединения: UP — добавить пира в группы; DOWN — удалить. */
static void topo_group_conn_status(struct ETCP_CONN* conn, int status, void* arg) {
struct TOPO_GROUPS* groups = (struct TOPO_GROUPS*)arg;
if (!conn || !groups) return;
if (status == ETCP_CONN_STATUS_REINIT) {
// Сброс normalizer мог уничтожить TABLE_REQUEST/NODEINFO. Повторяем обмен, сохраняя членство и пути.
for (struct ll_entry* e = groups->group_list->head; e; e = e->next) {
struct TOPO_GROUP* g = (struct TOPO_GROUP*)e;
for (struct ll_entry* se = g->senders_list->head; se; se = se->next) {
struct TOPO_GROUP_CONN_ITEM* peer = (struct TOPO_GROUP_CONN_ITEM*)se->data;
if (peer->conn != conn) continue;
if (topo_group_reset_exchange(peer) < 0) continue;
DEBUG_INFO(DEBUG_CATEGORY_BGP,"BGP session resync: peer=%016llx group=%016llx",
(unsigned long long)conn->peer_node_id,(unsigned long long)g->group_id);
topo_group_send_join_group(g, conn);
break;
}
}
return;
}
if (status == ETCP_CONN_STATUS_UP && conn->peer_node_id) {
/* UTUN присоединяет пир через UP принадлежащего ему NCD handle.
* Сам по себе транспортный UP не означает участия в UTUN-группе. */
if (!topo_configured(conn->instance, conn->peer_node_id)) topo_group_send_resync(conn);
/* CHAT: только члены каналов (peers_* в БД). */
sqlite3* db = conn->instance ? conn->instance->topo_sqlite_db : NULL;
uint64_t* chs = NULL; int chn = 0;
if (db && topo_node_sqlite_get_member_channels(db, conn->peer_node_id, &chs, &chn) == 0 && chn > 0) {
for (int i = 0; i < chn; i++) {
struct TOPO_GROUP* cg = topo_groups_find(groups, chs[i]);
if (cg && cg->group_type == TOPO_GROUP_TYPE_CHAT)
topo_group_begin_conn(cg, conn, 0);
}
}
u_free(chs);
} else if (status == ETCP_CONN_STATUS_DOWN || status == ETCP_CONN_STATUS_DELETE) {
struct ll_entry* fe = groups->group_list->head;
while (fe) { struct TOPO_GROUP* g = (struct TOPO_GROUP*)fe; fe = fe->next;
topo_group_remove_conn(g, conn, TOPO_REMOVE_TRANSPORT_DOWN);
}
}
}
/* Создаёт группу: очереди nodes/senders, local_node, conn_mgr, broadcast. */
static struct TOPO_GROUP* topo_group_create(struct UTUN_INSTANCE* instance, uint64_t group_id, uint8_t group_type) {
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "instance is NULL"); return NULL; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group_id=%016llx node_id=%016llx", (unsigned long long)group_id, (unsigned long long)instance->node_id);
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP));
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc failed"); return NULL; }
struct TOPO_GROUP* group = (struct TOPO_GROUP*)qe;
memset((uint8_t*)group + sizeof(struct ll_entry), 0, sizeof(*group) - sizeof(struct ll_entry));
group->group_id = group_id;
group->group_type = group_type;
group->instance = instance;
if (sc_derive_ed25519_pubkey(instance->my_keys.private_key, group->ed25519_public_key) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot derive group identity group=%016llx", (unsigned long long)group_id);
queue_entry_free(qe); return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Ed25519 pubkey derived from X25519 private key");
group->nodes = queue_new(instance->ua, BGP_NODES_HASH_SIZE, offsetof(struct TOPO_GROUP_NODE, node_id) - sizeof(struct ll_entry), 8, "group_nodes");
if (!group->nodes) { queue_entry_free(qe); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nodes queue creation failed"); return NULL; }
group->senders_list = queue_new(instance->ua, 0, 0, 0, "group_senders");
if (!group->senders_list) { queue_free(group->nodes); queue_entry_free(qe); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "senders_list queue creation failed"); return NULL; }
group->local_node = NULL;
if (topo_group_update_my_nodeinfo(instance, group) < 0) goto fail;
group->conn_mgr = conn_mgr_init(group);
if (!group->conn_mgr) goto fail;
if (broadcast_init(group) < 0 || radio_init(group) < 0) goto fail;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "TOPO group created: group_id=%016llx", (unsigned long long)group_id);
return group;
fail:
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group creation rolled back group=%016llx local_node=%p conn_mgr=%p broadcast=%p radio=%p",
(unsigned long long)group_id, (void*)group->local_node, (void*)group->conn_mgr,
(void*)group->broadcast, (void*)group->radio);
topo_group_destroy(group); queue_entry_free(qe); return NULL;
}
/* Полностью разрушает группу: recovery, connect, broadcast, conn_mgr, узлы. */
static void topo_group_destroy(struct TOPO_GROUP* group) {
if (!group) return;
group->stopping = 1;
multicast_close_group(group->instance, group->group_id);
nat_detection_cancel_group(group->instance->nat_det, group->group_id);
stream_cancel_group(group->instance, group->group_id);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 0 enter grp=%p id=0x%016llx rec_list=%p connect=%p",
group, (unsigned long long)group->group_id, group->recovery, group->connect);
topo_recovery_cancel_all(group);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 1 recovery_cancel done grp=%p", group);
topo_group_connect_destroy(group);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 2 connect_destroy done");
broadcast_destroy(group);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 2b broadcast_destroy done");
radio_destroy(group);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 2c radio_destroy done");
/* A stopped service must withdraw membership even if another group keeps ETCP alive. */
if (group->instance->running) {
struct ll_entry* current = group->senders_list->head;
while (current) {
struct TOPO_GROUP_CONN_ITEM* peer = (struct TOPO_GROUP_CONN_ITEM*)current->data;
current = current->next;
if (peer->conn && peer->conn->links_up) topo_group_leave_peer(group, peer->handle);
}
}
struct ll_entry* e;
while ((e = queue_data_get(group->senders_list)) != NULL) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "group destroy: release peer=%016llx group=%016llx",
(unsigned long long)node_conn_direct_node_id(item->handle), (unsigned long long)group->group_id);
topo_group_peer_free(item);
queue_entry_free(e);
}
queue_free(group->senders_list);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 3 senders_list done");
etcp_router_close_group(group->instance, group->group_id);
if (group->conn_mgr) { conn_mgr_destroy(group->conn_mgr); group->conn_mgr = NULL; }
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 4 conn_mgr done");
if (group->nodes) {
int nc = 0;
struct ll_entry* e = group->nodes->head;
while (e) {
struct ll_entry* next = e->next;
topo_nodeq_remove_node(group, (struct TOPO_GROUP_NODE*)e);
e = next; nc++;
}
queue_free(group->nodes);
if (nc) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 4b nodes cleaned (%d)", nc);
}
if (group->local_node) {
route_delete(group->instance->rt, group->local_node);
topo_nodeq_clear_paths(group->local_node);
topo_nodeq_free_group_fields(group->instance->topo_groups, group->local_node);
u_free(group->local_node);
}
/* освободить подписчиков BGP-событий (member_sync и др.) */
while (group->node_cbks) {
struct topo_node_cbk_entry* c = group->node_cbks;
group->node_cbks = c->next; u_free(c);
}
while (group->peer_ready_cbks) {
struct topo_peer_ready_cbk* cb = group->peer_ready_cbks;
group->peer_ready_cbks = cb->next; u_free(cb);
}
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 5 done");
}
/* Удаляет группу и все принадлежащие ей ресурсы. */
void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id) {
if (!g || !g->group_list) return;
struct TOPO_GROUP* group = topo_groups_find(g, group_id);
if (!group) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "remove_group: group %016llx not found", (unsigned long long)group_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "remove_group: id=%016llx type=%d ch=%s",
(unsigned long long)group_id, group->group_type, group->channel_id);
if (g->instance->conn_mgr == group->conn_mgr) g->instance->conn_mgr = NULL;
queue_remove_data(g->group_list, &group->ll);
topo_group_destroy(group); /* node_cbks освобождаются внутри */
queue_entry_free(&group->ll);
}
/* Инициализация контейнера групп: реестр, пулы, SQLite и подписки; группы создают сервисы. */
struct TOPO_GROUPS* topo_groups_init(struct UTUN_INSTANCE* instance) {
if (!instance || instance->topo_groups || instance->topo_sqlite_db) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid or already initialized topology/SQLite instance=%p", (void*)instance); return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_id=%016llx", (unsigned long long)instance->node_id);
struct TOPO_GROUPS* g = u_calloc(1, sizeof(struct TOPO_GROUPS));
if (!g) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc failed"); return NULL; }
g->instance = instance;
g->group_list = queue_new(instance->ua, 16, 0, 8, "topo_groups");
if (!g->group_list) { u_free(g); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group_list queue creation failed"); return NULL; }
g->node_registry = queue_new(instance->ua, TOPO_NODE_REGISTRY_HASH_SIZE, 0, 8, "node_registry");
if (!g->node_registry) { queue_free(g->group_list); u_free(g); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node_registry queue creation failed"); return NULL; }
instance->topo_groups = g;
g->v4_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA4), "to_v4sock");
g->v4_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR4), "to_v4addr");
g->v6_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA6), "to_v6sock");
g->v6_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR6), "to_v6addr");
g->v4_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET4), "to_v4sub");
g->v6_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET6), "to_v6sub");
if (!g->v4_sock_meta_pool || !g->v4_addr_pool || !g->v6_sock_meta_pool || !g->v6_addr_pool ||
!g->v4_subnet_pool || !g->v6_subnet_pool) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot initialize topology memory pools"); goto fail;
}
{
char db_file[512] = ":memory:";
if (instance->config && instance->config->global.db_path[0])
snprintf(db_file, sizeof(db_file), "%s/chats.db", instance->config->global.db_path);
int rc = sqlite3_open_v2(db_file, &instance->topo_sqlite_db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
if (rc == SQLITE_OK && instance->topo_sqlite_db) {
rc = sqlite3_exec(instance->topo_sqlite_db, "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;"
" PRAGMA synchronous=NORMAL; PRAGMA wal_autocheckpoint=10000; PRAGMA cache_size=-32768;"
" PRAGMA mmap_size=134217728; PRAGMA temp_store=MEMORY", NULL, NULL, NULL);
if (rc != SQLITE_OK || topo_node_sqlite_init(instance->topo_sqlite_db) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot initialize SQLite %s: rc=%d %s",
db_file, rc, sqlite3_errmsg(instance->topo_sqlite_db)); goto fail;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SQLite opened: %s rc=%d db=%p", db_file, rc, (void*)instance->topo_sqlite_db);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "sqlite3_open(%s) failed: %s",
db_file, instance->topo_sqlite_db ? sqlite3_errmsg(instance->topo_sqlite_db) : "null db");
goto fail;
}
}
if (topo_node_init_self(instance) < 0) goto fail;
etcp_bind(instance, ETCP_ID_TOPO_ENTRY, topo_group_receive_cbk);
etcp_add_conn_status_cbk(instance, topo_group_conn_status, g);
etcp_add_socket_cbk(instance, topo_node_on_socket_changed, NULL,
ETCP_SOCKET_EVENT_ADDR_CHANGED | ETCP_SOCKET_EVENT_STATUS_CHANGED);
utun_add_activity_cbk(instance, topo_group_on_activity_change, NULL);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "TOPO registry initialized (services create their own groups)");
return g;
fail:
topo_groups_destroy(instance);
if (instance->topo_sqlite_db) { sqlite3_close(instance->topo_sqlite_db); instance->topo_sqlite_db = NULL; }
return NULL;
}
/* Callback смены active/standby: обновляет свой nodeinfo и рассылает его пирам. */
void topo_group_on_activity_change(struct UTUN_INSTANCE* instance, int active, void* arg) {
(void)active; (void)arg;
if (!instance || !instance->topo_groups || !instance->topo_groups->group_list) return;
struct ll_entry* ge = instance->topo_groups->group_list->head;
while (ge) {
struct TOPO_GROUP* g = (struct TOPO_GROUP*)ge;
topo_group_update_my_nodeinfo(instance, g);
if (g->senders_list) {
struct ll_entry* se = g->senders_list->head;
while (se) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)se->data;
if (item && item->conn) topo_group_send_nodeinfo(g, g->local_node, item->conn);
se = se->next;
}
}
ge = ge->next;
}
}
/* Полный teardown контейнера: отписки, уничтожение групп, реестра, пулов. */
void topo_groups_destroy(struct UTUN_INSTANCE* instance) {
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "instance is NULL"); return; }
if (!instance->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "topo_groups is NULL"); return; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_id=%016llx tgs=%p", (unsigned long long)instance->node_id, instance->topo_groups);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 0 enter");
etcp_unbind(instance, ETCP_ID_TOPO_ENTRY);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 1 etcp_unbind done");
etcp_remove_conn_status_cbk(instance, topo_group_conn_status, instance->topo_groups);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 2 conn_cbk_remove done");
etcp_remove_socket_cbk(instance, topo_node_on_socket_changed, NULL);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 3 socket_cbk_remove done");
utun_remove_activity_cbk(instance, topo_group_on_activity_change, NULL);
route_connectivity_cancel_all(instance);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 4 connectivity_cancel done");
struct TOPO_GROUPS* g = instance->topo_groups;
struct ll_entry* ge;
int gcount = 0;
while ((ge = queue_data_get(g->group_list)) != NULL) { struct TOPO_GROUP* grp = (struct TOPO_GROUP*)ge; topo_group_destroy(grp); queue_entry_free(ge); gcount++; }
queue_free(g->group_list);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 5 groups destroyed (%d)", gcount);
if (g->node_registry) {
int ncount = 0;
struct ll_entry* re;
while ((re = queue_data_get(g->node_registry)) != NULL) {
struct TOPO_NODE* node;
memcpy(&node, re->data + 8, sizeof(node));
topo_node_destroy(g, node);
queue_entry_free(re); ncount++;
}
queue_free(g->node_registry);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 6 node_registry done (%d)", ncount);
} else {
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 6 node_registry skip (NULL)");
}
memory_pool_destroy(g->v4_sock_meta_pool);
memory_pool_destroy(g->v4_addr_pool);
memory_pool_destroy(g->v6_sock_meta_pool);
memory_pool_destroy(g->v6_addr_pool);
memory_pool_destroy(g->v4_subnet_pool);
memory_pool_destroy(g->v6_subnet_pool);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 7 pools destroyed");
/* Shared SQLite DB закрывается в utun_instance_destroy() в самом конце —
* chat/media/db_sync ещё используют его во время teardown. */
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 8 sqlite deferred (закрывается в utun_instance_destroy)");
u_free(g); instance->topo_groups = NULL; instance->conn_mgr = NULL;
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[TOPO_DESTROY] 9 done");
}
/* Возвращает UTUN-группу по умолчанию (group_id=TOPO_GROUP_UTUN). */
struct TOPO_GROUP* topo_groups_get_default(struct TOPO_GROUPS* g) {
return topo_groups_find(g, TOPO_GROUP_UTUN);
}
/* Ищет группу по group_id (NULL, если нет). */
struct TOPO_GROUP* topo_groups_find(struct TOPO_GROUPS* g, uint64_t group_id) {
if (!g || !g->group_list) return NULL;
struct ll_entry* e = queue_find_data_by_index(g->group_list, &group_id);
return e ? (struct TOPO_GROUP*)e : NULL;
}
/* Создаёт и регистрирует новую группу; добавляет существующие conn, для CHAT — автоподключение. */
struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t group_id, uint8_t group_type, const char* channel_id) {
if (!g || !g->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return NULL; }
if (topo_groups_find(g, group_id)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group_id=%016llx already exists", (unsigned long long)group_id); return NULL; }
struct TOPO_GROUP* group = topo_group_create(g->instance, group_id, group_type);
if (!group) return NULL;
if (channel_id) { strncpy(group->channel_id, channel_id, sizeof(group->channel_id) - 1); group->channel_id[sizeof(group->channel_id) - 1] = '\0'; }
queue_data_put_with_index(g->group_list, &group->ll);
struct ll_queue* connections = g->instance->connections;
if (connections) {
sqlite3* db = g->instance->topo_sqlite_db;
for (uint32_t slot = 0; slot < connections->hash_size; slot++) {
struct ll_entry* entry = connections->hash_table[slot];
while (entry) {
struct conn_queue_entry* cqe = (struct conn_queue_entry*)entry->data;
if (cqe->conn && cqe->peer_node_id != 0) {
if (group_type == TOPO_GROUP_TYPE_CHAT && db && cqe->conn->links_up && !cqe->conn->close_requested) {
/* CHAT: добавляем conn только если пир — член именно этого канала */
uint64_t* chs = NULL; int chn = 0;
if (topo_node_sqlite_get_member_channels(db, cqe->peer_node_id, &chs, &chn) == 0) {
for (int i = 0; i < chn; i++) {
if (chs[i] == group_id) { topo_group_new_conn(group, cqe->conn); break; }
}
}
u_free(chs);
} else if (group_type == TOPO_GROUP_TYPE_UTUN && cqe->conn->links_up) {
if (topo_configured(g->instance, cqe->peer_node_id)) topo_group_new_conn(group, cqe->conn);
}
}
entry = entry->hash_next;
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Group created: group_id=%016llx type=%u ch_id=%s", (unsigned long long)group_id, group_type, group->channel_id);
if (group_type == TOPO_GROUP_TYPE_CHAT && chat_setting_get_int(group->instance, "group_autoconnect", 1)) topo_group_connect_init(group);
return group;
}
/* Разрешить/запретить NAT-проверку локальных подсетей (делегат в nat_detection). */
void topo_group_set_nat_check_local(struct TOPO_GROUP* group, int allow) {
if (!group || !group->instance || !group->instance->nat_det) return;
nat_detection_set_allow_local(group->instance->nat_det, allow);
}
/* ── Рация (PTT walkie-talkie, per-CHAT-группа) ── */
void topo_group_set_radio(struct TOPO_GROUP* group, int on) {
if (!group || !group->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "set_radio: invalid args"); return; }
if (group->group_type != TOPO_GROUP_TYPE_CHAT) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "set_radio: only CHAT groups support radio grp=%016llx type=%u",
(unsigned long long)group->group_id, group->group_type);
return;
}
uint8_t v = on ? 1 : 0;
if (group->radio_active == v) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "set_radio: unchanged (%u) grp=%016llx ch=%s", v,
(unsigned long long)group->group_id, group->channel_id);
return;
}
uint8_t previous = group->radio_active;
group->radio_active = v;
if (topo_group_update_my_nodeinfo(group->instance, group) < 0) {
group->radio_active = previous;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "set_radio: cannot update local NODEINFO grp=%016llx ch=%s radio=%u",
(unsigned long long)group->group_id, group->channel_id, v);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "set_radio: %s grp=%016llx ch=%s",
v ? "ON" : "OFF", (unsigned long long)group->group_id, group->channel_id);
/* Свой мембер обновляется через те же подписки, что и удалённые BGP-узлы. */
for (struct topo_node_cbk_entry* cb = group->node_cbks; cb; cb = cb->next)
cb->fn(group, group->instance->node_id, TOPO_NODE_EVENT_UPDATE, cb->arg);
if (group->senders_list) {
struct ll_entry* se = group->senders_list->head;
while (se) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)se->data;
if (item && item->conn && group->local_node)
topo_group_send_nodeinfo(group, group->local_node, item->conn);
se = se->next;
}
}
}
int topo_group_radio_subscribers(struct TOPO_GROUP* group) {
if (!group || !group->nodes) return 0;
int n = 0;
struct ll_entry* e = group->nodes->head;
while (e) {
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e;
if (nq->radio && nq->node_id != group->instance->node_id) n++;
e = e->next;
}
return n;
}
/* Устанавливает колбэк сохранения узла в БД (используется chatgui/member_sync). */
void topo_groups_set_node_updated_cb(struct TOPO_GROUPS* groups, topo_node_updated_fn fn) {
if (groups) groups->node_updated_cb = fn;
}
/* Добавляет пира в группу при UP: дедуп, добавление в senders, запрос таблицы. */
static int topo_group_begin_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, int retain) {
if (!group || !conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; }
if (group->stopping) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "join on stopped group"); return -1; }
if (conn->instance != group->instance || !conn->links_up || conn->close_requested) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "group join needs a live transport of the same instance peer=%016llx group=%016llx",
(unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id); return -1;
}
if (!conn->instance->rt) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance->rt is NULL"); return -1; }
if (!topo_group_peer_allowed(group, conn->peer_node_id)) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group connect deferred: member unknown peer=%016llx group=%016llx",
(unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id);
return -1;
}
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (peer && retain) peer->retained = 1;
/* дедуп: тот же conn стреляет ETCP_CONN_STATUS_UP дважды (UDP-линк, затем TCP-линк).
* Если conn уже в senders_list — повторно не обрабатываем, иначе active_conn_count
* задваивается и переподключение никогда не стартует. */
{ struct ll_entry* se = group->senders_list ? group->senders_list->head : NULL;
while (se) {
if (((struct TOPO_GROUP_CONN_ITEM*)se->data)->conn == conn) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group_new_conn: conn already in senders_list, skip double UP (%s)", conn->log_name);
return 0;
}
se = se->next;
}
}
struct TOPO_GROUP_NODE* peer_nq = topo_node_find_by_id(group, conn->peer_node_id);
if (peer_nq) {
peer_nq->connectivity.last_ping_time = get_time_tb();
peer_nq->conn_presence |= NCONN_DIRECT;
peer_nq->conn_up |= NCONN_DIRECT;
if (conn->instance->topo_sqlite_db) topo_node_sqlite_nodeinfo_updated(conn->instance->topo_sqlite_db, conn->peer_node_id);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group_new_conn: ADD conn=%p peer=%016llx state=%d grp=%016llx ch=%s",
(void*)conn, (unsigned long long)conn->peer_node_id, conn->state,
(unsigned long long)group->group_id, group->channel_id);
if (topo_group_add_to_senders(group, conn) != 0) return -1;
topo_group_peer_progressed(group, conn->peer_node_id);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id);
peer = topo_group_peer(group, conn->peer_node_id);
if (topo_group_reset_exchange(peer) < 0) return -1;
topo_group_send_join_group(group, conn);
topo_group_connect_on_up(group, conn);
return 0;
}
int topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
return topo_group_begin_conn(group, conn, 1);
}
/* Обрабатывает DOWN пира: чистит пути, каскадно удаляет узлы, шлёт withdraw. */
void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, enum topo_group_remove_reason reason) {
if (!group || !conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return; }
if (!conn->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid instance"); return; }
const char* reason_text = "unknown";
switch (reason) {
case TOPO_REMOVE_TRANSPORT_DOWN: reason_text = "transport-down"; break;
case TOPO_REMOVE_LOCAL_LEAVE: reason_text = "local-left-group"; break;
case TOPO_REMOVE_REMOTE_LEAVE: reason_text = "peer-left-group"; break;
case TOPO_REMOVE_MEMBER_INVALID: reason_text = "membership-invalid"; break;
case TOPO_REMOVE_REJECTED: reason_text = "group-join-rejected"; break;
}
struct TOPO_GROUP_CONN_ITEM* pending = topo_group_peer(group, conn->peer_node_id);
if (pending && !pending->conn && node_conn_direct_get_conn(pending->handle) == conn) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP pending connection lost: peer=%016llx group=%016llx reason=%s",
(unsigned long long)pending->node_id, (unsigned long long)group->group_id, reason_text);
if (reason == TOPO_REMOVE_TRANSPORT_DOWN && pending->requests) {
pending->transport_down = 1; pending->retained = 0;
topo_recovery_changed(group); topo_group_connect_changed(group);
} else topo_group_pending_remove(pending);
return;
}
bool found_in_list = false;
struct ll_entry* e = group->senders_list->head;
while (e) { struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; if (item->conn == conn) { found_in_list = true; break; } e = e->next; }
if (!found_in_list) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "topo_group_remove_conn: conn=%p peer=%016llx state=%d NOT in senders_list (already removed?), skip grp=%016llx ch=%s", (void*)conn, (unsigned long long)conn->peer_node_id, conn->state, (unsigned long long)group->group_id, group->channel_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP peer session lost; removing its routes: peer=%016llx group=%016llx channel=%s "
"reason=%s links_up=%u close_requested=%u reinit_pending=%u",
(unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->channel_id,
reason_text, conn->links_up, conn->close_requested, conn->reinit_pending);
/* Больше не публикуем в закрываемую сессию, даже если общий транспорт ещё UP. */
pending->transport_down = 1;
topo_group_notify_ready(pending, 0);
int nodes_removed = 0;
int cascaded = 0;
struct ll_entry* node_entry = group->nodes ? group->nodes->head : NULL;
while (node_entry) {
struct ll_entry* next = node_entry->next;
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)node_entry;
{
uint64_t next_hop = nq->node_id;
uint16_t lost_rtt = UINT16_MAX;
for (struct ll_entry* pe = nq->paths ? nq->paths->head : NULL; pe; pe = pe->next) {
struct TOPO_NODEPATH* path = (struct TOPO_NODEPATH*)pe;
if (path->conn != conn) continue;
/* links_up уже сброшен; сохраняем последний RTT до удаления пути. */
if (conn->rtt_last) {
uint32_t total = (uint32_t)conn->rtt_last + path->cumulative_rtt;
lost_rtt = total > UINT16_MAX ? UINT16_MAX : (uint16_t)total;
}
if (path->hop_count >= 2) {
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(*path));
if (hop[path->hop_count - 1] == conn->peer_node_id) next_hop = hop[path->hop_count - 2];
}
break;
}
int removed = topo_group_remove_path(nq, conn);
if (!removed) { node_entry = next; continue; }
if (!nq->paths) {
uint64_t key = nq->node_id;
if (reason == TOPO_REMOVE_TRANSPORT_DOWN && key != conn->peer_node_id) {
topo_recovery_add_node(group, key, next_hop, lost_rtt); cascaded++;
}
nodes_removed++;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP destination unreachable; no alternative path: node=%016llx group=%016llx "
"lost_peer=%016llx reason=%s",
(unsigned long long)key, (unsigned long long)group->group_id,
(unsigned long long)conn->peer_node_id, reason_text);
// REINIT временно убирает путь, но end-to-end данные роутера остаются неподтверждёнными.
if (!conn->reinit_pending) etcp_router_conn_close_all_for_node(group->instance, group->group_id, key);
}
topo_group_routes_changed(group, nq, TOPO_NODE_EVENT_UPDATE);
}
node_entry = next;
}
/* освобождаем владение BGP: сначала убираем item из senders_list (чтобы
ре-ентрантный remove_conn от conn DOWN не нашёл его), затем закрываем handle. */
e = group->senders_list->head;
while (e) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
if (item->conn == conn) {
if (reason == TOPO_REMOVE_TRANSPORT_DOWN && item->requests && node_conn_direct_get_conn(item->handle) == conn) {
topo_exchange_cancel(item);
topo_tx_clear(item); topo_adverts_clear(item); item->conn = NULL; item->transport_down = 1; item->retained = 0;
item->accepted = 0; item->table_sent = 0; item->table_received = 0;
topo_recovery_changed(group); topo_group_connect_changed(group);
} else {
queue_remove_data(group->senders_list, e); topo_group_peer_free(item); queue_entry_free(e);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group_remove_conn: REMOVED conn=%p peer=%016llx from senders_list grp=%016llx ch=%s",
(void*)conn, (unsigned long long)conn->peer_node_id,
(unsigned long long)group->group_id, group->channel_id);
break;
}
e = e->next;
}
if (cascaded > 0) topo_recovery_start(group);
topo_group_connect_on_down(group, conn);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP peer removal complete: peer=%016llx group=%016llx reason=%s "
"destinations_without_paths=%d recovery_targets=%d",
(unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, reason_text, nodes_removed, cascaded);
}
// ============================================================================
// NODEINFO BASED IMPLEMENTATION
// ============================================================================
/* Ищет лучшее ETCP-соединение до узла (мин. hop_count среди живых путей). */
struct ETCP_CONN* topo_group_find_conn_for_node(struct TOPO_GROUP* group, uint64_t node_id) {
if (!group) return NULL;
struct TOPO_NODEPATH* path = topo_node_best_path(topo_node_find_by_id(group, node_id));
return path ? path->conn : NULL;
}
/* Добавляет путь (conn + hop_list + rtt) в paths узла. */
int topo_group_add_path(struct TOPO_GROUP_NODE* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count, uint16_t cumulative_rtt) {
if (!nq || !conn || !hop_count || hop_count > MAX_HOPS || !hop_list) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "add_path: invalid args"); return -1;
}
size_t path_size = sizeof(struct TOPO_NODEPATH) - sizeof(struct ll_entry) + hop_count * 8;
struct ll_entry* pe = queue_entry_new(path_size);
if (!pe) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "path allocation failed"); return -1; }
if (!nq->paths) nq->paths = queue_new(conn->instance->ua, 0, 0, 0, "node_paths");
if (!nq->paths) { queue_entry_free(pe); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "paths queue allocation failed"); return -1; }
struct TOPO_NODEPATH* path = (struct TOPO_NODEPATH*)pe;
path->conn = conn; path->hop_count = hop_count; path->cumulative_rtt = cumulative_rtt;
memcpy((uint64_t*)((uint8_t*)path + sizeof(struct TOPO_NODEPATH)), hop_list, hop_count * 8);
/* Все выделения завершены: прежний путь теперь можно заменить без потери при OOM. */
for (struct ll_entry* e = nq->paths->head; e;) {
struct ll_entry* next = e->next;
if (((struct TOPO_NODEPATH*)e)->conn == conn) { queue_remove_data(nq->paths, e); queue_entry_free(e); }
e = next;
}
queue_data_put(nq->paths, pe);
return 0;
}
/* Сосед владеет только своим объявлением; возвращаем число удалённых путей. */
int topo_group_remove_path(struct TOPO_GROUP_NODE* nq, struct ETCP_CONN* conn) {
if (!nq || !conn || !nq->paths) return 0;
int removed = 0;
for (struct ll_entry* e = nq->paths->head; e;) {
struct ll_entry* next = e->next;
if (((struct TOPO_NODEPATH*)e)->conn == conn) { queue_remove_data(nq->paths, e); queue_entry_free(e); removed++; }
e = next;
}
if (removed > 0 && queue_entry_count(nq->paths) == 0) { queue_free(nq->paths); nq->paths = NULL; }
return removed;
}
/* Одна точка обновления доступности, потребителей и анонсов после изменения путей. */
static int topo_group_routes_changed(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, int event) {
uint64_t id = node->node_id;
int result = 0;
struct TOPO_NODEPATH* best = topo_node_best_path(node);
node->conn_presence &= ~(NCONN_BGP | NCONN_DIRECT);
node->conn_up &= ~(NCONN_BGP | NCONN_DIRECT);
if (node->paths) node->conn_presence |= NCONN_BGP;
if (best) node->conn_up |= NCONN_BGP;
for (struct ll_entry* e = node->paths ? node->paths->head : NULL; e; e = e->next) {
struct TOPO_NODEPATH* p = (struct TOPO_NODEPATH*)e;
if (p->conn->peer_node_id != id) continue;
node->conn_presence |= NCONN_DIRECT;
if (p->conn->links_up && !p->conn->close_requested) node->conn_up |= NCONN_DIRECT;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP route recomputed: group=%016llx node=%016llx result=%s stored_paths=%d via=%016llx hops=%u",
(unsigned long long)group->group_id, (unsigned long long)id,
best ? (best->conn->peer_node_id == id ? "direct" : "via-intermediary") : "unreachable-no-live-path",
node->paths ? queue_entry_count(node->paths) : 0,
(unsigned long long)(best ? best->conn->peer_node_id : 0), best ? best->hop_count : 0);
if (group->group_type != TOPO_GROUP_TYPE_CHAT && group->instance->rt) {
route_delete(group->instance->rt, node);
if (best && node->subnets && node->subnets->v4_subnets && !route_insert(group->instance->rt, node)) {
result = -1;
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "BGP IPv4 routes rejected: group=%016llx node=%016llx; previous routes removed",
(unsigned long long)group->group_id, (unsigned long long)id);
}
}
topo_fire_nodeinfo_cbk(group->instance, group, node);
if (!node->paths) {
event = TOPO_NODE_EVENT_REMOVE;
if (group->instance->control_srv) control_server_notify_node_removed(group->instance->control_srv, id);
topo_nodeq_remove_node(group, node);
} else if (group->instance->control_srv) control_server_notify_node_change(group->instance->control_srv, node);
for (struct topo_node_cbk_entry* c = group->node_cbks; c; c = c->next) c->fn(group, id, event, c->arg);
topo_group_publish_route(group, id);
topo_recovery_changed(group);
conn_mgr_routes_changed(group->conn_mgr);
multicast_routes_changed(group->instance);
return result;
}
/* TABLE_COMPLETE подтверждает только NODEINFO текущего обмена. Сбой обмена
* делает недостоверными все пути этого соседа, включая уже полученную часть. */
static void topo_group_prune_paths(struct TOPO_GROUP_CONN_ITEM* peer, int all) {
struct TOPO_GROUP* group = peer->group;
for (struct ll_entry* e = group->nodes->head; e;) {
struct ll_entry* next = e->next;
struct TOPO_GROUP_NODE* node = (struct TOPO_GROUP_NODE*)e;
int remove = 0;
for (struct ll_entry* p = node->paths ? node->paths->head : NULL; p; p = p->next) {
struct TOPO_NODEPATH* path = (struct TOPO_NODEPATH*)p;
if (path->conn == peer->conn && (all || path->exchange != peer->local_epoch)) remove = 1;
}
if (remove) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "prune peer route: group=%016llx node=%016llx peer=%016llx reason=%s",
(unsigned long long)group->group_id, (unsigned long long)node->node_id,
(unsigned long long)peer->node_id, all ? "exchange_timeout" : "not_in_snapshot");
topo_group_remove_path(node, peer->conn);
topo_group_routes_changed(group, node, TOPO_NODE_EVENT_UPDATE);
}
e = next;
}
}
// ===== NODEINFO process =====
/* Шлёт пиру ошибку несоответствия типа группы (ERR_GROUP_MISMATCH). */
static void topo_group_send_err_group_mismatch(struct TOPO_GROUP* group, struct ETCP_CONN* conn, uint8_t expected_type, uint8_t received_flags) {
if (!group || !conn) return;
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (!peer || !peer->accepted) return;
struct TOPOMSG_ERR_GROUP_MISMATCH msg = { .h = topo_header(peer, TOPO_SUBCMD_ERR_GROUP_MISMATCH),
.expected_type = expected_type, .received_flags = received_flags };
topo_send_control(conn, &msg, sizeof(msg));
}
/* Обрабатывает NODEINFO: проверка подписи/версии, обновление узла, пути, роутинг, форвард. */
int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from, const uint8_t* data, size_t len) {
if (!group || !group->instance || !from || from->instance != group->instance || !data ||
len < sizeof(struct TOPOMSG_NODEINFO_PKT)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid NODEINFO context or length=%zu", len); return -1;
}
struct TOPOMSG_NODEINFO_PKT* pkt = (struct TOPOMSG_NODEINFO_PKT*)data;
struct TOPOMSG_NODE* ni = &pkt->node;
if (ni->group_id != group->group_id) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO group_id mismatch from %s: expected=%016llx got=%016llx nid=%016llx",
from->log_name, (unsigned long long)group->group_id, (unsigned long long)ni->group_id, (unsigned long long)ni->node_id);
return -1;
}
// Проверка соответствия типа группы
uint8_t expected_flags = (group->group_type == TOPO_GROUP_TYPE_CHAT) ? 0 : TOPO_FLAG_SEND_SUBNETS;
if ((ni->flags & TOPO_FLAG_SEND_SUBNETS) != expected_flags) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Group type mismatch with %s: expected_flags=0x%02x received_flags=0x%02x (local_type=%u)",
from->log_name, expected_flags, ni->flags, group->group_type);
topo_group_send_err_group_mismatch(group, from, group->group_type, ni->flags);
return -1;
}
int dyn = topo_node_dyn_size(ni);
if (dyn < 0) return -1; // Причина уже записана в topo_node_dyn_size.
if ((size_t)dyn != len - sizeof(struct TOPOMSG_NODEINFO_PKT)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO size mismatch from=%s node=%016llx length=%zu expected=%zu",
from->log_name, (unsigned long long)ni->node_id, len, sizeof(struct TOPOMSG_NODEINFO_PKT) + (size_t)dyn);
return -1;
}
uint64_t node_id = ni->node_id;
if (ni->hop_count >= MAX_HOPS) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO from %s dropped: too many hops (%d)", from->log_name, ni->hop_count); return -1; }
if (node_id == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO node_id=0 from %s grp=%016llx — rejected (malformed)",
from->log_name, (unsigned long long)group->group_id);
return -1;
}
if (node_id == group->instance->node_id) return 0;
struct TOPO_GROUP_NODE* nodeinfo1 = topo_node_find_by_id(group, node_id);
uint8_t new_ver = ni->ver;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO: node=%016llx ver=%d v4s=%d v4a=%d v6s=%d v6a=%d hops=%d from=%s",
(unsigned long long)node_id, new_ver, ni->local_v4_sockets, ni->local_v4_addrs,
ni->local_v6_sockets, ni->local_v6_addrs, ni->hop_count, from->log_name);
struct ll_queue* paths = NULL;
int is_new_node = 0;
if (nodeinfo1) {
paths = nodeinfo1->paths;
} else { is_new_node = 1; }
const uint8_t* ser_data = data + sizeof(struct TOPOMSG_HEADER);
size_t ser_len = len - sizeof(struct TOPOMSG_HEADER);
struct TOPO_NODE* new_ni = NULL;
struct TOPO_NODESUBNETS* new_subnets = NULL;
uint64_t* new_hop_list = NULL; uint8_t new_hop_count = 0;
uint16_t incoming_cumulative_rtt = 0;
if (topo_node_deserialize(group, ser_data, ser_len, &new_ni, &new_subnets, &new_hop_list, &new_hop_count, &incoming_cumulative_rtt) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO deserialize failed from %s nid=%016llx", from->log_name, (unsigned long long)node_id);
return -1;
}
{ int v4c = topo_list_count((struct _topo_head*)new_ni->v4_addrs);
int v6c = topo_list_count((struct _topo_head*)new_ni->v6_addrs);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO deser: nid=%016llx v4a=%d v6a=%d from=%s",
(unsigned long long)node_id, v4c, v6c, from->log_name); }
/* Проверяем целую запись до изменения маршрутов и общего реестра. */
struct TOPO_NODE* current = topo_node_registry_find(group->instance->topo_groups, node_id);
int conflict = current && current->timestamp == new_ni->timestamp &&
memcmp(current->x25519_self_sig, new_ni->x25519_self_sig, 64);
if (topo_node_verify(new_ni) < 0 || conflict) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO rejected node=%016llx timestamp=%llu conflict=%d",
(unsigned long long)node_id, (unsigned long long)new_ni->timestamp, conflict);
topo_node_destroy(group->instance->topo_groups, new_ni);
struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets };
topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected);
u_free(new_hop_list);
return -1;
}
/* На проводе цепочка идёт от назначения до предыдущего узла, без отправителя.
* Нулевой hop_count допустим только для собственного анонса отправителя. */
int invalid_path = new_hop_count ? new_hop_list[0] != node_id : from->peer_node_id != node_id;
for (uint8_t i = 0; i < new_hop_count; i++) {
if (!new_hop_list[i] || new_hop_list[i] == group->instance->node_id || new_hop_list[i] == from->peer_node_id) invalid_path = 1;
for (uint8_t j = 0; j < i; j++) if (new_hop_list[j] == new_hop_list[i]) invalid_path = 1;
}
if (invalid_path) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO invalid path: group=%016llx node=%016llx from=%016llx hops=%u",
(unsigned long long)group->group_id, (unsigned long long)node_id,
(unsigned long long)from->peer_node_id, new_hop_count);
topo_node_destroy(group->instance->topo_groups, new_ni);
struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets };
topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected);
u_free(new_hop_list); return -1;
}
if (nodeinfo1 && new_ni->timestamp <= nodeinfo1->last_timestamp) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO unchanged/stale node=%016llx current=%llu incoming=%llu",
(unsigned long long)node_id, (unsigned long long)nodeinfo1->last_timestamp,
(unsigned long long)new_ni->timestamp);
uint64_t hops[MAX_HOPS];
if (new_hop_count) memcpy(hops, new_hop_list, new_hop_count * sizeof(*hops));
hops[new_hop_count] = from->peer_node_id;
int result = topo_group_add_path(nodeinfo1, from, hops, new_hop_count + 1, incoming_cumulative_rtt);
topo_node_destroy(group->instance->topo_groups, new_ni);
struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets };
topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected);
u_free(new_hop_list);
if (result < 0) return -1;
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, from->peer_node_id);
((struct TOPO_NODEPATH*)nodeinfo1->paths->tail)->exchange = peer ? peer->local_epoch : 0;
result = topo_group_routes_changed(group, nodeinfo1, TOPO_NODE_EVENT_UPDATE);
topo_group_peer_progressed(group, from->peer_node_id);
return result;
}
uint64_t incoming_timestamp = new_ni->timestamp;
if (!nodeinfo1) {
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE));
if (!qe) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group node allocation failed");
topo_node_destroy(group->instance->topo_groups, new_ni);
struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets };
topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected);
u_free(new_hop_list); return -1;
}
nodeinfo1 = (struct TOPO_GROUP_NODE*)qe;
memset((uint8_t*)nodeinfo1 + sizeof(struct ll_entry), 0, sizeof(*nodeinfo1) - sizeof(struct ll_entry));
}
/* Подготовить новый путь до изменения записи/подсетей. OOM сохраняет прежний узел целиком. */
uint64_t hop_list[MAX_HOPS];
if (new_hop_count) memcpy(hop_list, new_hop_list, new_hop_count * sizeof(*hop_list));
hop_list[new_hop_count] = from->peer_node_id;
struct TOPO_GROUP_NODE prepared = {0};
if (topo_group_add_path(&prepared, from, hop_list, new_hop_count + 1, incoming_cumulative_rtt) < 0) {
topo_node_destroy(group->instance->topo_groups, new_ni);
struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets };
topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected);
if (is_new_node) queue_entry_free(&nodeinfo1->ll);
u_free(new_hop_list); return -1;
}
struct TOPO_NODE* stored = topo_node_registry_store(group->instance->topo_groups, new_ni);
if (!stored) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node registry update failed");
topo_nodeq_clear_paths(&prepared);
topo_node_destroy(group->instance->topo_groups, new_ni);
struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets };
topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected);
if (is_new_node) queue_entry_free(&nodeinfo1->ll);
u_free(new_hop_list); return -1;
}
new_ni = stored;
if (!is_new_node) topo_nodeq_free_group_fields(group->instance->topo_groups, nodeinfo1);
nodeinfo1->node_id = node_id;
nodeinfo1->subnets = new_subnets;
if (is_new_node) queue_data_put_with_index(group->nodes, &nodeinfo1->ll);
if (is_new_node)
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node added to group: node=%016llx grp=%016llx type=%u ch=%s (bgp)",
(unsigned long long)node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id);
/* Остальные соседи сохраняются; после store больше не нужны выделения памяти. */
struct TOPO_NODEPATH* incoming = (struct TOPO_NODEPATH*)prepared.paths->head;
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, from->peer_node_id);
incoming->exchange = peer ? peer->local_epoch : 0;
if (paths) {
struct ll_entry* e;
while ((e = queue_data_get(paths))) {
if (((struct TOPO_NODEPATH*)e)->conn == from) queue_entry_free(e);
else queue_data_put(prepared.paths, e);
}
queue_free(paths);
}
nodeinfo1->paths = prepared.paths; nodeinfo1->last_ver = new_ver;
nodeinfo1->last_timestamp = incoming_timestamp;
nodeinfo1->radio = (ni->flags & TOPO_FLAG_RADIO) ? 1 : 0;
nodeinfo1->phone_status = ni->flags & (TOPO_FLAG_PHONE_KNOWN | TOPO_FLAG_PHONE_BUSY);
u_free(new_hop_list);
if (node_id != group->instance->node_id) {
sqlite3* sdb = group->instance->topo_sqlite_db;
if (sdb) {
if (topo_node_sqlite_snapshot_put(sdb, new_ni) < 0)
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot persist received NODEINFO node=%016llx", (unsigned long long)node_id);
/* узлы в nodes пишет только merkle-путь; BGP лишь дополняет IP для мемберов */
if (topo_node_sqlite_member_exists(sdb, node_id))
topo_node_sqlite_addrs_put(sdb, node_id, new_ni);
if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->channel_id[0]) {
/* упрощённый плейсхолдер (source=1): pubkeys из BGP, без подписей.
не участвует в merkle-хеше, дополняется полной merkle-записью */
topo_node_sqlite_member_placeholder_put(sdb, group->channel_id, node_id,
new_ni->public_key, new_ni->ed25519_public_key);
}
topo_node_sqlite_nodeinfo_updated(sdb, node_id);
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node_updated_cb: node=%016llx has_cb=%d ch=%s type=%d", (unsigned long long)node_id, group->instance->topo_groups->node_updated_cb ? 1 : 0, group->channel_id, group->group_type);
if (group->instance->topo_groups->node_updated_cb)
group->instance->topo_groups->node_updated_cb(group->instance, node_id, ni->public_key, ni->ed25519_public_key);
int result = topo_group_routes_changed(group, nodeinfo1, is_new_node ? TOPO_NODE_EVENT_NEW : TOPO_NODE_EVENT_UPDATE);
if (node_id != group->instance->node_id) {
struct TOPO_NODE* tni = topo_node_registry_find(group->instance->topo_groups, nodeinfo1->node_id);
int v4a = tni ? topo_list_count((struct _topo_head*)tni->v4_addrs) : 0;
int v6a = tni ? topo_list_count((struct _topo_head*)tni->v6_addrs) : 0;
if (v4a > 0 || v6a > 0) route_connectivity_probe_node(group->instance, group, nodeinfo1);
}
/* Создание линков может вызвать транспортные callbacks: структура группы уже обновлена. */
topo_group_peer_progressed(group, from->peer_node_id);
node_conn_direct_update_node(group->instance, node_id);
return result;
}
/* Обрабатывает WITHDRAW: удаляет узел/пути, чистит роутинг, форвардит withdraw. */
int topo_group_process_withdraw(struct TOPO_GROUP* group, struct ETCP_CONN* sender, const uint8_t* data, size_t len) {
if (!group || !group->instance || !sender || sender->instance != group->instance || !data || len != sizeof(struct TOPOMSG_WITHDRAW_PKT)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid WITHDRAW len=%zu", len); return -1;
}
struct TOPOMSG_WITHDRAW_PKT* wp = (struct TOPOMSG_WITHDRAW_PKT*)data;
uint64_t node_id = wp->node_id;
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, node_id);
if (!nq) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "WITHDRAW unknown node=%016llx", (unsigned long long)node_id); return 0; }
int removed = topo_group_remove_path(nq, sender);
int remaining = nq->paths ? queue_entry_count(nq->paths) : 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP WITHDRAW received (sender withdrew its route): group=%016llx node=%016llx "
"source=%016llx removed=%d remaining_stored_paths=%d",
(unsigned long long)group->group_id, (unsigned long long)node_id,
(unsigned long long)sender->peer_node_id, removed, remaining);
if (removed) topo_group_routes_changed(group, nq, TOPO_NODE_EVENT_UPDATE);
return 0;
}
/* Сериализует актуальный анонс; кэш меняется только после успешной передачи ETCP. */
static int topo_emit_nodeinfo(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, struct ETCP_CONN* conn, uint16_t cumulative_rtt) {
if (!group || !node || !conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "send_nodeinfo: invalid arguments"); return -1; }
size_t max_sz = sizeof(struct TOPOMSG_NODEINFO_PKT) + TOPO_NODE_WIRE_MAX_SIZE;
uint8_t* p = u_malloc(max_sz);
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "send_nodeinfo: allocation failed"); return -1; }
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (!peer || !peer->accepted) { u_free(p); return 0; } /* initial exchange sends the current state after ACCEPT */
struct TOPOMSG_HEADER h = topo_header(peer, TOPO_SUBCMD_NODEINFO);
memcpy(p, &h, sizeof(h));
struct TOPO_NODE* sni = topo_node_registry_find(group->instance->topo_groups, node->node_id);
if (!sni) {
DEBUG_WARN(DEBUG_CATEGORY_BGP,
"send_nodeinfo: node %016llx NOT in registry — skip forward to %s grp=%016llx type=%d ch=%s is_local=%d paths=%d nodes_total=%d",
(unsigned long long)node->node_id, conn->log_name,
(unsigned long long)group->group_id, group->group_type,
group->channel_id[0] ? group->channel_id : "-",
(node == group->local_node) ? 1 : 0,
node->paths ? queue_entry_count(node->paths) : -1,
group->nodes ? queue_entry_count(group->nodes) : -1);
u_free(p);
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "send_nodeinfo: node %016llx ver=%d grp=%016llx to conn=%p name='%s'", (unsigned long long)node->node_id, sni->ver, (unsigned long long)group->group_id, (void*)conn, conn->log_name);
uint8_t sflags = (group->group_type == TOPO_GROUP_TYPE_CHAT) ? 0 : TOPO_FLAG_SEND_SUBNETS;
/* RTT не определяет маршрут. Его непрерывные колебания не должны запускать
* волну NODEINFO при каждом принятом альтернативном пути. Хешируем с RTT=0. */
int ser_len = topo_node_serialize(sni, node, group->group_id, sflags, p + sizeof(h), max_sz - sizeof(h), 0);
if (ser_len < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "send_nodeinfo: serialize failed for node %016llx", (unsigned long long)node->node_id);
u_free(p); return -1;
}
if (!peer->advertised) peer->advertised = queue_new(group->instance->ua, BGP_NODES_HASH_SIZE, 0, 8, "topo_advertised");
if (!peer->advertised) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "advertised queue allocation failed"); u_free(p); return -1; }
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256(p + sizeof(h), ser_len, digest);
struct topo_advert* advert = (struct topo_advert*)queue_find_data_by_index(peer->advertised, &node->node_id);
if (advert && memcmp(advert->digest, digest, sizeof(digest)) == 0) { u_free(p); return 0; }
((struct TOPOMSG_NODEINFO_PKT*)p)->node.cumulative_rtt = cumulative_rtt;
int fresh = advert == NULL;
if (fresh) advert = (struct topo_advert*)queue_entry_new(sizeof(*advert) - sizeof(struct ll_entry));
if (!advert) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "advert allocation failed"); u_free(p); return -1; }
struct ll_entry* e = queue_entry_new(0);
if (!e) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "send_nodeinfo: entry allocation failed");
if (fresh) queue_entry_free(&advert->ll);
u_free(p); return -1;
}
e->dgram = p; e->len = (size_t)ser_len + sizeof(h);
if (etcp_send(conn, e) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "send_nodeinfo: etcp_send FAILED for node %016llx to %s",
(unsigned long long)node->node_id, conn->log_name);
if (fresh) queue_entry_free(&advert->ll);
u_free(p); queue_entry_free(e); return -1;
}
advert->node_id = node->node_id; memcpy(advert->digest, digest, sizeof(digest));
if (fresh) queue_data_put_with_index(peer->advertised, &advert->ll);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NODEINFO sent: group=%016llx node=%016llx to=%016llx rtt=%u",
(unsigned long long)group->group_id, (unsigned long long)node->node_id, (unsigned long long)peer->node_id, cumulative_rtt);
return 1;
}
/* NODEINFO сериализуется только при отправке: указатели на узлы не переживают callback. */
int topo_group_send_nodeinfo(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, struct ETCP_CONN* conn) {
if (!group || !node || !conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "send_nodeinfo: invalid arguments"); return -1; }
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (!peer || !peer->accepted) return 0;
return topo_tx_node(peer, node->node_id);
}
enum topo_tx_kind { TOPO_TX_CONTROL, TOPO_TX_NODE, TOPO_TX_TABLE };
struct topo_tx_item {
struct topo_tx_item* next;
enum topo_tx_kind kind;
uint64_t node_id;
struct ll_entry* packet;
uint64_t* nodes;
size_t count, pos, sent;
};
static void topo_tx_item_free(struct topo_tx_item* item) {
if (item->packet) { queue_dgram_free(item->packet); queue_entry_free(item->packet); }
u_free(item->nodes); u_free(item);
}
static void topo_tx_clear(struct TOPO_GROUP_CONN_ITEM* peer) {
if (peer->tx_wake) { uasync_call_soon_cancel(peer->group->instance->ua, peer->tx_wake); peer->tx_wake = NULL; }
if (peer->conn && peer->conn->send_input_q) queue_waiter_cancel(peer->conn->send_input_q, &peer->tx_waiter);
while (peer->tx_head) {
struct topo_tx_item* item = peer->tx_head; peer->tx_head = item->next; topo_tx_item_free(item);
}
peer->tx_tail = NULL;
}
static void topo_tx_fail(struct TOPO_GROUP_CONN_ITEM* peer) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group sender failed: group=%016llx peer=%016llx epoch=%016llx",
(unsigned long long)peer->group->group_id, (unsigned long long)peer->node_id, (unsigned long long)peer->local_epoch);
topo_tx_clear(peer); peer->tx_failed = 1; peer->table_sent = 0;
topo_group_notify_ready(peer, 0);
topo_exchange_arm(peer);
topo_recovery_changed(peer->group);
topo_group_connect_changed(peer->group);
}
static void topo_tx_wait(void* arg);
static void topo_tx_schedule(struct TOPO_GROUP_CONN_ITEM* peer) {
if (!peer->tx_head || peer->tx_failed || peer->tx_wake || peer->tx_waiter.internal || peer->tx_waiter.call_soon_id) return;
peer->tx_wake = uasync_call_soon(peer->group->instance->ua, peer, topo_tx_wait);
if (!peer->tx_wake) topo_tx_fail(peer);
}
static int topo_tx_append(struct TOPO_GROUP_CONN_ITEM* peer, struct topo_tx_item* item) {
if (!item || peer->tx_failed) { if (item) topo_tx_item_free(item); topo_tx_fail(peer); return -1; }
if (peer->tx_tail) peer->tx_tail->next = item; else peer->tx_head = item;
peer->tx_tail = item; topo_tx_schedule(peer);
return peer->tx_failed ? -1 : 0;
}
static int topo_tx_control(struct TOPO_GROUP_CONN_ITEM* peer, const void* data, size_t size) {
struct topo_tx_item* item = u_calloc(1, sizeof(*item));
if (!item) { topo_tx_fail(peer); return -1; }
item->packet = ll_alloc_lldgram(size);
if (!item->packet) { u_free(item); topo_tx_fail(peer); return -1; }
memcpy(item->packet->dgram, data, size); item->packet->len = size;
item->kind = TOPO_TX_CONTROL;
return topo_tx_append(peer, item);
}
static int topo_tx_node(struct TOPO_GROUP_CONN_ITEM* peer, uint64_t node_id) {
/* Последнее изменение занимает своё место после уже ожидающих WITHDRAW.
* На один узел хранится не более одного несериализованного NODEINFO. */
struct topo_tx_item** link = &peer->tx_head;
struct topo_tx_item* prev = NULL;
struct topo_tx_item* item = NULL;
while (*link) {
if ((*link)->kind == TOPO_TX_NODE && (*link)->node_id == node_id) {
item = *link; *link = item->next;
if (peer->tx_tail == item) peer->tx_tail = prev;
item->next = NULL; break;
}
prev = *link; link = &(*link)->next;
}
if (!item) item = u_calloc(1, sizeof(*item));
if (!item) { topo_tx_fail(peer); return -1; }
item->kind = TOPO_TX_NODE; item->node_id = node_id;
return topo_tx_append(peer, item);
}
static int topo_tx_table(struct TOPO_GROUP_CONN_ITEM* peer) {
for (struct topo_tx_item* item = peer->tx_head; item; item = item->next)
if (item->kind == TOPO_TX_TABLE) return 0;
struct TOPO_GROUP* group = peer->group;
struct topo_tx_item* item = u_calloc(1, sizeof(*item));
if (!item) { topo_tx_fail(peer); return -1; }
item->kind = TOPO_TX_TABLE;
size_t capacity = (size_t)queue_entry_count(group->nodes) + 1;
item->nodes = u_malloc(capacity * sizeof(*item->nodes));
if (!item->nodes) { u_free(item); topo_tx_fail(peer); return -1; }
item->nodes[item->count++] = group->local_node->node_id;
for (struct ll_entry* e = group->nodes->head; e; e = e->next) {
struct TOPO_GROUP_NODE* node = (struct TOPO_GROUP_NODE*)e;
if (node != group->local_node && topo_group_should_send_to(node, peer->node_id)) item->nodes[item->count++] = node->node_id;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group table queued: group=%016llx peer=%016llx epoch=%016llx nodes=%zu",
(unsigned long long)group->group_id, (unsigned long long)peer->node_id, (unsigned long long)peer->local_epoch, item->count);
return topo_tx_append(peer, item);
}
static int topo_tx_emit_node(struct TOPO_GROUP_CONN_ITEM* peer, uint64_t id) {
struct TOPO_GROUP* group = peer->group;
struct TOPO_GROUP_NODE* node = id == group->local_node->node_id ? group->local_node : topo_node_find_by_id(group, id);
if (!node || (node != group->local_node && !topo_group_should_send_to(node, peer->node_id))) {
struct ll_entry* previous = peer->advertised ? queue_find_data_by_index(peer->advertised, &id) : NULL;
if (!previous) return 0;
struct TOPOMSG_WITHDRAW_PKT wd = { .h = topo_header(peer, TOPO_SUBCMD_WITHDRAW), .node_id = id };
if (topo_emit_control(peer->conn, &wd, sizeof(wd)) < 0) return -1;
queue_remove_data(peer->advertised, previous); queue_entry_free(previous);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "WITHDRAW sent: group=%016llx node=%016llx to=%016llx",
(unsigned long long)group->group_id, (unsigned long long)id, (unsigned long long)peer->node_id);
return 1;
}
return topo_emit_nodeinfo(group, node, peer->conn, node == group->local_node ? 0 : topo_get_chain_rtt(node));
}
static void topo_tx_ready(struct ll_queue* queue, void* arg) {
struct TOPO_GROUP_CONN_ITEM* peer = arg;
/* Deferred waiters must recheck: another producer can occupy the queue first. */
if (queue_entry_count(queue)) { topo_tx_schedule(peer); return; }
struct topo_tx_item* item = peer->tx_head;
if (!item) return;
int result = 0, complete = 1;
if (item->kind == TOPO_TX_CONTROL) {
result = etcp_send(peer->conn, item->packet);
if (result == 0) item->packet = NULL;
} else if (item->kind == TOPO_TX_NODE) result = topo_tx_emit_node(peer, item->node_id);
else {
if (item->pos < item->count) {
result = topo_tx_emit_node(peer, item->nodes[item->pos++]);
if (result > 0) item->sent++;
complete = 0;
} else {
result = topo_group_send_table_complete(peer->group, peer->conn);
if (result == 0) {
peer->table_sent = 1; topo_group_log_exchange(peer->group, peer);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "group table sent: group=%016llx peer=%016llx epoch=%016llx nodes=%zu",
(unsigned long long)peer->group->group_id, (unsigned long long)peer->node_id,
(unsigned long long)peer->local_epoch, item->sent);
}
}
}
if (result < 0) { topo_tx_fail(peer); return; }
topo_group_peer_progressed(peer->group, peer->node_id);
if (complete) {
peer->tx_head = item->next;
if (!peer->tx_head) peer->tx_tail = NULL;
topo_tx_item_free(item);
}
topo_tx_schedule(peer);
}
static void topo_tx_wait(void* arg) {
struct TOPO_GROUP_CONN_ITEM* peer = arg;
peer->tx_wake = NULL;
if (!peer->conn || !peer->conn->send_input_q || peer->conn->close_requested ||
queue_waiter_wait(peer->conn->send_input_q, &peer->tx_waiter, topo_tx_ready, peer) < 0) topo_tx_fail(peer);
}
/* Небольшой ответ вне групповой сессии владеет NCD только до постановки пакета
* в транспорт. CLOSED/DOWN отменяет waiter до уничтожения транспортной очереди. */
struct topo_control {
struct NODE_CONN_DIRECT* handle;
struct ll_queue* queue;
struct queue_waiter_handle waiter;
struct ll_entry* packet;
};
static void topo_control_free(struct topo_control* control) {
queue_waiter_cancel(control->queue, &control->waiter);
node_conn_direct_close(control->handle);
queue_dgram_free(control->packet); queue_entry_free(control->packet); u_free(control);
}
static void topo_control_event(struct NODE_CONN_DIRECT* handle, enum ncd_event event, void* arg) {
(void)handle;
if (event == NCD_EVENT_UP) return;
DEBUG_WARN(DEBUG_CATEGORY_BGP, "pending group control cancelled transport_event=%d", event);
topo_control_free(arg);
}
static void topo_control_ready(struct ll_queue* queue, void* arg) {
struct topo_control* control = arg;
if (queue_entry_count(queue)) {
if (queue_waiter_wait(queue, &control->waiter, topo_control_ready, control) >= 0) return;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot rearm group control waiter");
} else if (etcp_send(node_conn_direct_get_conn(control->handle), control->packet) == 0) control->packet = NULL;
else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot send pending group control");
topo_control_free(control);
}
static int topo_control_wait(struct ETCP_CONN* conn, const void* data, size_t size) {
if (!conn->send_input_q || conn->close_requested) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "group control on closing transport"); return -1; }
if (!queue_entry_count(conn->send_input_q)) return topo_emit_control(conn, data, size);
struct topo_control* control = u_calloc(1, sizeof(*control));
if (!control) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group control allocation failed"); return -1; }
control->queue = conn->send_input_q; control->packet = ll_alloc_lldgram(size);
if (!control->packet) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group control packet allocation failed"); u_free(control); return -1; }
memcpy(control->packet->dgram, data, size); control->packet->len = size;
if (node_conn_direct_open(conn->instance, conn->peer_node_id, topo_control_event, control, &control->handle, NULL) == NCD_ERR ||
queue_waiter_wait(control->queue, &control->waiter, topo_control_ready, control) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot retain pending group control"); topo_control_free(control); return -1;
}
return 0;
}
/* Добавляет conn в senders_list (дедуп), если его там ещё нет. */
static int topo_group_add_to_senders(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
if (!group || !conn || !group->senders_list) return -1;
struct TOPO_GROUP_CONN_ITEM* pending = topo_group_peer(group, conn->peer_node_id);
if (pending) {
if (pending->conn && pending->conn != conn) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group peer has conflicting transport peer=%016llx", (unsigned long long)conn->peer_node_id);
return -1;
}
pending->conn = conn; return 0;
}
for (struct ll_entry* e = group->senders_list->head; e; e = e->next)
if (((struct TOPO_GROUP_CONN_ITEM*)e->data)->conn == conn) return 0;
struct ll_entry* e = queue_entry_new(sizeof(struct TOPO_GROUP_CONN_ITEM));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "senders_add: allocation failed"); return -1; }
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
item->group = group; item->node_id = conn->peer_node_id;
item->conn = conn; item->handle = NULL; item->retained = 1; item->progress = get_time_tb();
if (node_conn_direct_open(conn->instance, conn->peer_node_id, NULL, NULL, &item->handle, NULL) == NCD_ERR) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "senders_add: cannot acquire NCD handle peer=%016llx", (unsigned long long)conn->peer_node_id);
queue_entry_free(e); return -1;
}
queue_data_put(group->senders_list, e);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "senders_add: acquired peer=%016llx group=%016llx",
(unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id);
return 0;
}
/* Решает, надо ли слать узел пиру (не шлём, если пир уже есть в hop_list пути). */
static bool topo_group_should_send_to(const struct TOPO_GROUP_NODE* nq, uint64_t target_id) {
if (!nq) return false;
if (target_id == nq->node_id) return false;
struct TOPO_NODEPATH* path = topo_node_best_path(nq);
if (!path || path->hop_count >= MAX_HOPS) return false;
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(*path));
for (uint8_t i = 0; i < path->hop_count; i++) if (hop[i] == target_id) return false;
return true;
}
int topo_group_join_peer(struct TOPO_GROUP* group, struct NODE_CONN_DIRECT* handle) {
struct ETCP_CONN* conn = node_conn_direct_get_conn(handle);
if (!group || !conn || !conn->links_up) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "join peer requires a connected NCD handle"); return -1;
}
return topo_group_new_conn(group, conn);
}
struct topo_leave {
struct TOPOMSG_HEADER message;
struct UTUN_INSTANCE* instance;
uint64_t group_id;
struct NODE_CONN_DIRECT* handle;
struct ll_queue* queue;
struct queue_waiter_handle waiter;
};
static void topo_leave_finish(struct topo_leave* leave) {
queue_waiter_cancel(leave->queue, &leave->waiter);
node_conn_direct_close(leave->handle);
u_free(leave);
}
static void topo_leave_event(struct NODE_CONN_DIRECT* handle, enum ncd_event event, void* arg) {
(void)handle;
if (event == NCD_EVENT_UP) return;
struct topo_leave* leave = arg;
DEBUG_WARN(DEBUG_CATEGORY_BGP, "LEAVE_GROUP cancelled: transport event=%d group=%016llx",
event, (unsigned long long)leave->group_id);
/* NCD CLOSED приходит до уничтожения send_input_q; waiter ещё можно отменить. */
topo_leave_finish(leave);
}
static void topo_leave_ready(struct ll_queue* queue, void* arg) {
struct topo_leave* leave = arg;
if (queue_entry_count(queue)) {
if (queue_waiter_wait(queue, &leave->waiter, topo_leave_ready, leave) >= 0) return;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot rearm LEAVE_GROUP waiter");
topo_leave_finish(leave); return;
}
struct ETCP_CONN* conn = node_conn_direct_get_conn(leave->handle);
struct TOPO_GROUP* group = topo_groups_find(leave->instance->topo_groups, leave->group_id);
if (group && topo_group_has_sender(group, conn)) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "LEAVE_GROUP superseded by a new membership group=%016llx",
(unsigned long long)leave->group_id);
topo_leave_finish(leave); return;
}
int result = topo_emit_control(conn, &leave->message, sizeof(leave->message));
if (result != 0) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot send LEAVE_GROUP group=%016llx", (unsigned long long)leave->group_id);
else DEBUG_INFO(DEBUG_CATEGORY_BGP, "LEAVE_GROUP sent: peer=%016llx group=%016llx",
(unsigned long long)node_conn_direct_node_id(leave->handle), (unsigned long long)leave->group_id);
topo_leave_finish(leave);
}
int topo_group_leave_peer(struct TOPO_GROUP* group, struct NODE_CONN_DIRECT* handle) {
struct ETCP_CONN* conn = node_conn_direct_get_conn(handle);
if (!group || !conn) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "leave peer without group/connection"); return -1; }
if (!topo_group_has_sender(group, conn)) return 0;
if (!conn->send_input_q || conn->close_requested) {
topo_group_remove_conn(group, conn, TOPO_REMOVE_LOCAL_LEAVE); return 0;
}
struct topo_leave* leave = u_calloc(1, sizeof(*leave));
if (!leave) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LEAVE_GROUP allocation failed");
topo_group_remove_conn(group, conn, TOPO_REMOVE_LOCAL_LEAVE); return -1;
}
leave->message = topo_header(topo_group_peer(group, conn->peer_node_id), TOPO_SUBCMD_LEAVE_GROUP);
leave->instance = group->instance; leave->group_id = group->group_id; leave->queue = conn->send_input_q;
if (node_conn_direct_open(group->instance, conn->peer_node_id, topo_leave_event, leave, &leave->handle, NULL) == NCD_ERR) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot retain NCD handle for LEAVE_GROUP"); u_free(leave);
topo_group_remove_conn(group, conn, TOPO_REMOVE_LOCAL_LEAVE); return -1;
}
topo_group_remove_conn(group, conn, TOPO_REMOVE_LOCAL_LEAVE);
/* Отправка единственного управляющего пакета после освобождения общей очереди.
Контекст не зависит от срока жизни группы; новый JOIN отменяет устаревший LEAVE. */
if (queue_waiter_wait(leave->queue, &leave->waiter, topo_leave_ready, leave) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot wait to send LEAVE_GROUP"); topo_leave_finish(leave); return -1;
}
return 0;
}
/* Повторный REQUEST_TABLE той же сессии не создаёт второй снимок. */
static void topo_group_handle_request_table(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (peer && peer->accepted && !peer->table_sent) topo_tx_table(peer);
}
/* Обрабатывает JOIN_GROUP: для UTUN — добавить запросившего и инициировать BGP обратно. */
static void topo_group_handle_join_group(struct TOPO_GROUP* group, struct ETCP_CONN* conn, const struct TOPOMSG_JOIN_GROUP* msg) {
if (topo_group_begin_conn(group, conn, 0) < 0) { topo_group_reject(conn, &msg->h, TOPO_JOIN_UNAVAILABLE); return; }
struct TOPO_GROUP_CONN_ITEM* peer = topo_group_peer(group, conn->peer_node_id);
if (peer->peer_epoch && peer->peer_epoch != msg->h.src_epoch && peer->accepted) {
if (topo_group_reset_exchange(peer) < 0) return;
topo_group_send_join_group(group, conn);
}
peer->peer_epoch = msg->h.src_epoch;
struct TOPOMSG_JOIN_GROUP reply = { .h = topo_header(peer, TOPO_SUBCMD_JOIN_ACCEPT), .group_type = group->group_type };
topo_send_control(conn, &reply, sizeof(reply));
}
/* Обрабатывает RESYNC: если мы VPN-клиент пира — заново шлём JOIN_GROUP по группам. */
static void topo_group_handle_resync(struct UTUN_INSTANCE* instance, struct ETCP_CONN* conn) {
if (!instance || !conn || !conn->instance || !instance->topo_groups) return;
/* общий сигнал от пассивной стороны: рестартуем обмен, если мы для этого пира VPN-клиент */
if (!topo_configured(instance, conn->peer_node_id)) return;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "handle_resync: from %s — re-announce groups", conn->log_name);
struct ll_entry* fe = instance->topo_groups->group_list->head;
while (fe) {
struct TOPO_GROUP* g = (struct TOPO_GROUP*)fe; fe = fe->next;
if (g->group_type != TOPO_GROUP_TYPE_CHAT)
topo_group_send_join_group(g, conn);
}
}
/* ── BGP node event callbacks ── */
/* Регистрирует подписчика на события узлов группы (NEW/UPDATE/REMOVE). */
int topo_group_add_peer_ready_cbk(struct TOPO_GROUP* group, topo_peer_ready_fn fn, void* arg) {
if (!group || !fn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer readiness subscription: invalid arguments"); return -1; }
struct topo_peer_ready_cbk* cb = u_malloc(sizeof(*cb));
if (!cb) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer readiness subscription allocation failed"); return -1; }
cb->fn = fn; cb->arg = arg; cb->next = group->peer_ready_cbks; group->peer_ready_cbks = cb;
return 0;
}
void topo_group_remove_peer_ready_cbk(struct TOPO_GROUP* group, topo_peer_ready_fn fn, void* arg) {
for (struct topo_peer_ready_cbk** p = group ? &group->peer_ready_cbks : NULL; p && *p; p = &(*p)->next) {
if ((*p)->fn == fn && (*p)->arg == arg) {
struct topo_peer_ready_cbk* cb = *p; *p = cb->next; u_free(cb); return;
}
}
}
void topo_group_add_node_cbk(struct TOPO_GROUP* group, topo_node_event_fn fn, void* arg) {
if (!group || !fn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node subscription: invalid arguments"); return; }
struct topo_node_cbk_entry* e = u_malloc(sizeof(*e));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node subscription allocation failed"); return; }
e->fn = fn; e->arg = arg;
e->next = group->node_cbks;
group->node_cbks = e;
}
/* Снимает подписчика событий узлов группы. */
void topo_group_remove_node_cbk(struct TOPO_GROUP* group, topo_node_event_fn fn, void* arg) {
if (!group || !fn) return;
struct topo_node_cbk_entry** p = &group->node_cbks;
while (*p) {
if ((*p)->fn == fn && (*p)->arg == arg) {
struct topo_node_cbk_entry* r = *p;
*p = r->next; u_free(r);
return;
}
p = &(*p)->next;
}
}
/* ── Global nodeinfo callbacks (instance-level, any node change) ── */
/* Регистрирует глобальный колбэк изменения nodeinfo (уровень инстанса). */
void utun_add_nodeinfo_cbk(struct UTUN_INSTANCE* instance, nodeinfo_cbk_fn fn, void* arg) {
if (!instance || !fn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nodeinfo subscription: invalid arguments"); return; }
struct nodeinfo_cbk_entry* e = u_malloc(sizeof(*e));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nodeinfo subscription allocation failed"); return; }
e->fn = fn; e->arg = arg;
e->next = instance->nodeinfo_cbks;
instance->nodeinfo_cbks = e;
}
/* Снимает глобальный колбэк nodeinfo. */
void utun_remove_nodeinfo_cbk(struct UTUN_INSTANCE* instance, nodeinfo_cbk_fn fn, void* arg) {
if (!instance || !fn) return;
struct nodeinfo_cbk_entry** p = &instance->nodeinfo_cbks;
while (*p) {
if ((*p)->fn == fn && (*p)->arg == arg) {
struct nodeinfo_cbk_entry* r = *p;
*p = r->next; u_free(r);
return;
}
p = &(*p)->next;
}
}
/* Вызывает все глобальные nodeinfo-колбэки для узла. */
void topo_fire_nodeinfo_cbk(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node) {
if (!instance || !group || !node) return;
nat_detection_topology_changed(instance->nat_det);
struct nodeinfo_cbk_entry* c = instance->nodeinfo_cbks;
while (c) { c->fn(group, node, c->arg); c = c->next; }
}