#include #include #include #include "../lib/ll_queue.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/memory_pool.h" #include "utun_instance.h" #include "etcp.h" #include "config_parser.h" #include "topo_node.h" #include "topo_group.h" #include "route_connectivity.h" #include "route_lib.h" #include "etcp_debug.h" #include "topo_node_sqlite.h" #include "../transport_layer/etcp_connections.h" #include "../lib/u_async.h" #include "../ntp_time.h" /* Инкремент refcount узла: держит TOPO_NODE живым, пока на него ссылаются группы. */ void topo_node_ref(struct TOPO_NODE* ni) { if (!ni) return; ni->group_ref_count++; } /* Инкремент refcount узла в глобальном реестре по node_id (взял ссылку через реестр). */ void topo_node_registry_ref(struct TOPO_GROUPS* groups, uint64_t node_id) { struct TOPO_NODE* ni = topo_node_registry_find(groups, node_id); if (ni) ni->group_ref_count++; } /* Декремент refcount; при обнулении освобождает имя узла и сам TOPO_NODE. */ void topo_node_unref(struct TOPO_NODE* ni) { if (!ni) return; if (--ni->group_ref_count == 0) { u_free(ni->node_name); u_free(ni); } } /* Освобождают односвязные списки структур узла (sock_meta/адреса/подсети v4,v6) из memory_pool. */ static void free_v4_sock_list(struct memory_pool* pool, struct TOPO_SOCKMETA4* head) { while (head) { struct TOPO_SOCKMETA4* next = head->next; memory_pool_free(pool, head); head = next; } } static void free_v4_addr_list(struct memory_pool* pool, struct TOPO_ADDR4* head) { while (head) { struct TOPO_ADDR4* next = head->next; memory_pool_free(pool, head); head = next; } } static void free_v6_sock_list(struct memory_pool* pool, struct TOPO_SOCKMETA6* head) { while (head) { struct TOPO_SOCKMETA6* next = head->next; memory_pool_free(pool, head); head = next; } } static void free_v6_addr_list(struct memory_pool* pool, struct TOPO_ADDR6* head) { while (head) { struct TOPO_ADDR6* next = head->next; memory_pool_free(pool, head); head = next; } } static void free_v4_sub_list(struct memory_pool* pool, struct TOPO_SUBNET4* head) { while (head) { struct TOPO_SUBNET4* next = head->next; memory_pool_free(pool, head); head = next; } } static void free_v6_sub_list(struct memory_pool* pool, struct TOPO_SUBNET6* head) { while (head) { struct TOPO_SUBNET6* next = head->next; memory_pool_free(pool, head); head = next; } } /* Освобождает список reality-сокетов узла (выделялся u_calloc). */ static void free_reality_sock_list(struct TOPO_REALITY_SOCK* head) { while (head) { struct TOPO_REALITY_SOCK* next = head->next; u_free(head); head = next; } } /* Ищет reality-параметры сокета узла по socket_id (NULL, если сокет без reality). */ const struct TOPO_REALITY_SOCK* topo_node_find_reality_sock(const struct TOPO_NODE* ni, uint8_t socket_id) { if (!ni) return NULL; for (const struct TOPO_REALITY_SOCK* r = ni->reality_socks; r; r = r->next) if (r->socket_id == socket_id) return r; return NULL; } /* Копирует reality-параметры узла в link->reality и поднимает флаг reality_set. */ void topo_node_apply_reality(struct ETCP_LINK* link, const struct TOPO_REALITY_SOCK* rs) { if (!link || !rs) return; memcpy(link->reality.server_static_pubkey, rs->server_pubkey, REALITY_AUTH_KEY_SIZE); memcpy(link->reality.short_id, rs->short_id, REALITY_SHORT_ID_SIZE); memcpy(link->reality.version, rs->version, REALITY_VERSION_SIZE); snprintf(link->reality.server_name, sizeof(link->reality.server_name), "%s", rs->server_name); link->reality.fingerprint = REALITY_FP_CHROME; link->reality_set = 1; } /* Упорядоченное сравнение списков адресов/мета (порядок обхода сокетов детерминирован, prepend даёт одинаковый порядок при неизменном наборе сокетов). */ static int sockmeta4_list_equal(struct TOPO_SOCKMETA4* a, struct TOPO_SOCKMETA4* b) { while (a && b) { if (a->id != b->id || a->config_type != b->config_type || a->nat_type != b->nat_type) return 0; a = a->next; b = b->next; } return a == NULL && b == NULL; } static int addr4_list_equal(struct TOPO_ADDR4* a, struct TOPO_ADDR4* b) { while (a && b) { if (a->port != b->port || a->type != b->type || a->socket_id != b->socket_id || a->protocol != b->protocol || memcmp(a->addr, b->addr, 4) != 0) return 0; a = a->next; b = b->next; } return a == NULL && b == NULL; } static int sockmeta6_list_equal(struct TOPO_SOCKMETA6* a, struct TOPO_SOCKMETA6* b) { while (a && b) { if (a->id != b->id || a->config_type != b->config_type || a->nat_type != b->nat_type) return 0; a = a->next; b = b->next; } return a == NULL && b == NULL; } static int addr6_list_equal(struct TOPO_ADDR6* a, struct TOPO_ADDR6* b) { while (a && b) { if (a->port != b->port || a->type != b->type || a->socket_id != b->socket_id || a->protocol != b->protocol || memcmp(a->addr, b->addr, 16) != 0) return 0; a = a->next; b = b->next; } return a == NULL && b == NULL; } static int reality_sock_list_equal(struct TOPO_REALITY_SOCK* a, struct TOPO_REALITY_SOCK* b) { while (a && b) { if (a->socket_id != b->socket_id || memcmp(a->short_id, b->short_id, 8) != 0 || memcmp(a->server_pubkey, b->server_pubkey, 32) != 0 || memcmp(a->version, b->version, 3) != 0 || strncmp(a->server_name, b->server_name, TOPO_REALITY_SNAME_MAX) != 0) return 0; a = a->next; b = b->next; } return a == NULL && b == NULL; } /* извлекает hop_list из лучшего живого path в nq->paths (или любого, если живых нет). память принадлежит TOPO_NODEPATH в paths, не освобождать */ uint64_t* topo_node_best_hop_list(struct TOPO_GROUP_NODE* nq, uint8_t* out_count, uint16_t* out_rtt) { if (!nq || !nq->paths || !nq->paths->head) { *out_count = 0; if (out_rtt) *out_rtt = 0; return NULL; } struct TOPO_NODEPATH* best = NULL; uint8_t min_hops = 255; struct ll_entry* e = nq->paths->head; while (e) { struct TOPO_NODEPATH* p = (struct TOPO_NODEPATH*)e; if (p->conn && p->conn->links_up && p->hop_count < min_hops) { best = p; min_hops = p->hop_count; } e = e->next; } if (!best) best = (struct TOPO_NODEPATH*)nq->paths->head; *out_count = best->hop_count; if (out_rtt) *out_rtt = best->cumulative_rtt; return (uint64_t*)((uint8_t*)best + sizeof(struct TOPO_NODEPATH)); } /* Освобождает списки адресов (sock_meta + addrs + reality) узла в глобальном реестре. * Адреса — глобальная идентичность узла, шарится между группами через node_registry, * поэтому освобождаются только при последнем unref (topo_node_registry_unref). */ static void topo_node_identity_free(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni) { if (!groups || !ni) return; free_v4_sock_list(groups->v4_sock_meta_pool, ni->v4_sock_meta); free_v4_addr_list(groups->v4_addr_pool, ni->v4_addrs); free_v6_sock_list(groups->v6_sock_meta_pool, ni->v6_sock_meta); free_v6_addr_list(groups->v6_addr_pool, ni->v6_addrs); free_reality_sock_list(ni->reality_socks); ni->v4_sock_meta = NULL; ni->v4_addrs = NULL; ni->v6_sock_meta = NULL; ni->v6_addrs = NULL; ni->reality_socks = NULL; } /* Полностью освобождает TOPO_NODE со всеми списками и именем (без учёта refcount). */ void topo_node_free_raw(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni) { if (!ni) return; if (groups) { free_v4_sock_list(groups->v4_sock_meta_pool, ni->v4_sock_meta); free_v4_addr_list(groups->v4_addr_pool, ni->v4_addrs); free_v6_sock_list(groups->v6_sock_meta_pool, ni->v6_sock_meta); free_v6_addr_list(groups->v6_addr_pool, ni->v6_addrs); } free_reality_sock_list(ni->reality_socks); u_free(ni->node_name); u_free(ni); } /* Публичная обёртка free_raw — уничтожение узла глобального реестра. */ void topo_node_destroy(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni) { topo_node_free_raw(groups, ni); } /* Освобождает per-group поля TOPO_GROUP_NODE: unref идентичности в реестре + подсети, сброс handle. */ void topo_nodeq_free_group_fields(struct TOPO_GROUPS* groups, struct TOPO_GROUP_NODE* nq) { if (!groups || !nq) return; topo_node_registry_unref(groups, nq->node_id); if (nq->subnets) { free_v4_sub_list(groups->v4_subnet_pool, nq->subnets->v4_subnets); free_v6_sub_list(groups->v6_subnet_pool, nq->subnets->v6_subnets); u_free(nq->subnets); nq->subnets = NULL; } nq->handle = NULL; } /* Полное удаление узла из группы: отмена влётных проб, освобождение paths/subnets, * извлечение из group->nodes и освобождение самого nq. Единственный корректный способ удаления. */ void topo_nodeq_remove_node(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq) { if (!group || !nq) return; route_connectivity_cancel_node(nq); if (nq->paths) { queue_free(nq->paths); nq->paths = NULL; } queue_remove_data(group->nodes, &nq->ll); topo_nodeq_free_group_fields(group->instance ? group->instance->topo_groups : NULL, nq); queue_entry_free(&nq->ll); } // ===== глобальный реестр TOPO_NODE ===== /* Ищет TOPO_NODE в глобальном реестре по node_id (NULL, если узла нет). */ struct TOPO_NODE* topo_node_registry_find(struct TOPO_GROUPS* groups, uint64_t node_id) { if (!groups || !groups->node_registry) return NULL; struct ll_entry* e = queue_find_data_by_index(groups->node_registry, &node_id); if (!e) return NULL; struct TOPO_NODE* node; memcpy(&node, e->data + 8, sizeof(node)); return node; } /* Является ли входящий узел полной идентичностью (BGP NODEINFO / локальный узел). * Partial-узлы из invite/DB-load не несут ed25519 — по ним идентичность не перезаписываем. */ static int topo_node_is_full_identity(const struct TOPO_NODE* ni) { for (int i = 0; i < SC_PUBKEY_SIZE; i++) if (ni->ed25519_public_key[i]) return 1; return 0; } /* Кладёт узел в реестр; если он уже есть — обновляет идентичность/адреса и возвращает существующий. */ struct TOPO_NODE* topo_node_registry_store(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni) { if (!groups || !ni) return NULL; struct TOPO_NODE* existing = topo_node_registry_find(groups, ni->node_id); if (existing == ni) { topo_node_ref(existing); return existing; } if (ni->timestamp && topo_node_verify(ni) < 0) return NULL; if (existing) { topo_node_ref(existing); if (existing->timestamp || ni->timestamp) { if (ni->timestamp <= existing->timestamp) { if (ni->timestamp == existing->timestamp && memcmp(ni->x25519_self_sig, existing->x25519_self_sig, 64)) DEBUG_WARN(DEBUG_CATEGORY_BGP, "node timestamp conflict: node=%016llx timestamp=%llu", (unsigned long long)ni->node_id, (unsigned long long)ni->timestamp); else DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node unchanged/stale: node=%016llx current=%llu incoming=%llu", (unsigned long long)ni->node_id, (unsigned long long)existing->timestamp, (unsigned long long)ni->timestamp); topo_node_free_raw(groups, ni); return existing; } /* Сохраняем адрес объекта для заёмщиков, заменяем всё подписанное содержимое сразу. */ topo_node_identity_free(groups, existing); u_free(existing->node_name); struct ll_entry ll = existing->ll; uint32_t refs = existing->group_ref_count; *existing = *ni; existing->ll = ll; existing->group_ref_count = refs; u_free(ni); DEBUG_INFO(DEBUG_CATEGORY_BGP, "node snapshot updated: node=%016llx timestamp=%llu", (unsigned long long)existing->node_id, (unsigned long long)existing->timestamp); return existing; } /* Идентичность (ver/имя/pubkeys/подпись) — авторитетна у полного NODEINFO, * иначе реестр держит устаревший ver/имя и форвард nodeinfo ломает подпись. */ if (topo_node_is_full_identity(ni)) { existing->ver = ni->ver; existing->client_type = ni->client_type; existing->client_activity = ni->client_activity; memcpy(existing->public_key, ni->public_key, SC_PUBKEY_SIZE); memcpy(existing->ed25519_public_key, ni->ed25519_public_key, SC_PUBKEY_SIZE); memcpy(existing->x25519_self_sig, ni->x25519_self_sig, 64); if (ni->node_name) { if (existing->node_name) u_free(existing->node_name); existing->node_name = ni->node_name; ni->node_name = NULL; } } /* Адреса — глобальная идентичность. Обновляем каждый список, который входящий узел несёт * (частичные узлы заполняют только то, что знают, не затирая остальное). */ if (ni->v4_sock_meta) { free_v4_sock_list(groups->v4_sock_meta_pool, existing->v4_sock_meta); existing->v4_sock_meta = ni->v4_sock_meta; ni->v4_sock_meta = NULL; } if (ni->v4_addrs) { free_v4_addr_list(groups->v4_addr_pool, existing->v4_addrs); existing->v4_addrs = ni->v4_addrs; ni->v4_addrs = NULL; } if (ni->v6_sock_meta) { free_v6_sock_list(groups->v6_sock_meta_pool, existing->v6_sock_meta); existing->v6_sock_meta = ni->v6_sock_meta; ni->v6_sock_meta = NULL; } if (ni->v6_addrs) { free_v6_addr_list(groups->v6_addr_pool, existing->v6_addrs); existing->v6_addrs = ni->v6_addrs; ni->v6_addrs = NULL; } if (ni->reality_socks) { free_reality_sock_list(existing->reality_socks); existing->reality_socks = ni->reality_socks; ni->reality_socks = NULL; } topo_node_free_raw(groups, ni); return existing; } struct ll_entry* qe = queue_entry_new(8 + sizeof(struct TOPO_NODE*)); if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "registry_acquire: queue_entry_new failed for node=%016llx", (unsigned long long)ni->node_id); return NULL; } if (groups->instance && groups->instance->topo_sqlite_db) { struct TOPO_NODE* saved = topo_node_sqlite_snapshot_load(groups->instance->topo_sqlite_db, groups, ni->node_id); if (saved && saved->timestamp >= ni->timestamp) { if (saved->timestamp == ni->timestamp && memcmp(saved->x25519_self_sig, ni->x25519_self_sig, 64)) DEBUG_WARN(DEBUG_CATEGORY_BGP, "persisted node timestamp conflict node=%016llx", (unsigned long long)ni->node_id); topo_node_free_raw(groups, ni); ni = saved; } else topo_node_free_raw(groups, saved); } topo_node_ref(ni); memcpy(qe->data, &ni->node_id, 8); memcpy(qe->data + 8, &ni, sizeof(ni)); queue_data_put_with_index(groups->node_registry, qe); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "registry_store: node=%016llx NEW v4=%d v6=%d", (unsigned long long)ni->node_id, topo_list_count((struct _topo_head*)ni->v4_addrs), topo_list_count((struct _topo_head*)ni->v6_addrs)); return ni; } /* Снимает ссылку группы на узел; при последней — чистит идентичность и удаляет запись из реестра. */ void topo_node_registry_unref(struct TOPO_GROUPS* groups, uint64_t node_id) { if (!groups) return; struct TOPO_NODE* ni = topo_node_registry_find(groups, node_id); if (!ni) return; uint32_t prev_ref = ni->group_ref_count; if (prev_ref == 1) topo_node_identity_free(groups, ni); topo_node_unref(ni); if (prev_ref == 1) { struct ll_entry* e = groups->node_registry ? queue_find_data_by_index(groups->node_registry, &node_id) : NULL; if (e) { queue_remove_data(groups->node_registry, e); queue_entry_free(e); } } } // ===== serialization ===== /* Собирает канонический буфер для Ed25519-подписи узла (id, ver, pubkey, имя, тип, адреса). */ int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size) { if (!ni || !buf || buf_size < 64 + 9) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "build_sig_msg: invalid args"); return -1; } size_t off = 0; memcpy(buf + off, &ni->node_id, 8); off += 8; buf[off++] = ni->ver; if (off + 8 + SC_PUBKEY_SIZE > buf_size) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "signature buffer too small"); return -1; } for (int i = 7; i >= 0; i--) buf[off++] = (uint8_t)(ni->timestamp >> (i * 8)); memcpy(buf + off, ni->public_key, SC_PUBKEY_SIZE); off += SC_PUBKEY_SIZE; size_t name_len = ni->node_name ? strlen(ni->node_name) : 0; if (name_len > 63) name_len = 63; if (off + name_len + 1 > buf_size) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "build_sig_msg: buf too small for name"); return -1; } if (name_len) { memcpy(buf + off, ni->node_name, name_len); off += name_len; } buf[off++] = '\0'; if (off + 2 > buf_size) return -1; buf[off++] = ni->client_type; buf[off++] = ni->client_activity; { struct TOPO_SOCKMETA4* e = ni->v4_sock_meta; while (e) { if (off + 3 > buf_size) return -1; buf[off++] = e->id; buf[off++] = e->config_type; buf[off++] = e->nat_type; e = e->next; }} { struct TOPO_ADDR4* e = ni->v4_addrs; while (e) { if (off + 9 > buf_size) return -1; memcpy(buf + off, e->addr, 4); off += 4; buf[off++] = (uint8_t)(e->port & 0xFF); buf[off++] = (uint8_t)((e->port >> 8) & 0xFF); buf[off++] = e->type; buf[off++] = e->socket_id; buf[off++] = e->protocol; e = e->next; }} { struct TOPO_SOCKMETA6* e = ni->v6_sock_meta; while (e) { if (off + 3 > buf_size) return -1; buf[off++] = e->id; buf[off++] = e->config_type; buf[off++] = e->nat_type; e = e->next; }} { struct TOPO_ADDR6* e = ni->v6_addrs; while (e) { if (off + 21 > buf_size) return -1; memcpy(buf + off, e->addr, 16); off += 16; buf[off++] = (uint8_t)(e->port & 0xFF); buf[off++] = (uint8_t)((e->port >> 8) & 0xFF); buf[off++] = e->type; buf[off++] = e->socket_id; buf[off++] = e->protocol; e = e->next; }} { struct TOPO_REALITY_SOCK* e = ni->reality_socks; while (e) { size_t nlen = strnlen(e->server_name, TOPO_REALITY_SNAME_MAX); if (off + 1 + 8 + 32 + 3 + nlen + 1 > buf_size) return -1; buf[off++] = e->socket_id; memcpy(buf + off, e->short_id, 8); off += 8; memcpy(buf + off, e->server_pubkey, 32); off += 32; memcpy(buf + off, e->version, 3); off += 3; if (nlen) { memcpy(buf + off, e->server_name, nlen); off += nlen; } buf[off++] = '\0'; e = e->next; }} return (int)off; } /* Подписывает свой узел Ed25519: build_sig_msg + sc_ed25519_sign → ni->x25519_self_sig. */ int topo_node_sign_self(struct UTUN_INSTANCE* instance, struct TOPO_NODE* ni) { if (!instance || !ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid signing arguments"); return -1; } uint64_t old_timestamp = ni->timestamp; uint8_t old_sig[64]; memcpy(old_sig, ni->x25519_self_sig, sizeof(old_sig)); uint64_t previous = ni->timestamp; if (instance->topo_sqlite_db) { uint64_t saved = topo_node_sqlite_snapshot_timestamp(instance->topo_sqlite_db, ni->node_id); if (saved > previous) previous = saved; } int64_t now = ntp_time_get_us(instance); if (previous >= INT64_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node timestamp exhausted"); return -1; } ni->timestamp = now > 0 && (uint64_t)now > previous ? (uint64_t)now : previous + 1; uint8_t sig_msg[TOPO_SIG_MSG_MAX_SIZE]; int sig_len = topo_node_build_sig_msg(ni, sig_msg, sizeof(sig_msg)); if (sig_len <= 0 || sc_ed25519_sign(instance->my_ed25519_privkey, sig_msg, (size_t)sig_len, ni->x25519_self_sig) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ed25519 self-sign FAILED for node=%016llx", (unsigned long long)ni->node_id); ni->timestamp = old_timestamp; memcpy(ni->x25519_self_sig, old_sig, sizeof(old_sig)); return -1; } if (instance->topo_sqlite_db && topo_node_sqlite_snapshot_put(instance->topo_sqlite_db, ni) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot persist local node snapshot node=%016llx", (unsigned long long)ni->node_id); ni->timestamp = old_timestamp; memcpy(ni->x25519_self_sig, old_sig, sizeof(old_sig)); return -1; } DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node signed: node=%016llx timestamp=%llu", (unsigned long long)ni->node_id, (unsigned long long)ni->timestamp); return 0; } int topo_node_verify(const struct TOPO_NODE* ni) { uint8_t msg[TOPO_SIG_MSG_MAX_SIZE]; if (!ni || !ni->timestamp || ni->timestamp > INT64_MAX || ni->node_id != sc_derive_node_id_from_pubkey(ni->public_key)) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid signed node identity or timestamp"); return -1; } int len = topo_node_build_sig_msg((struct TOPO_NODE*)ni, msg, sizeof(msg)); if (len <= 0 || sc_ed25519_verify(ni->ed25519_public_key, msg, (size_t)len, ni->x25519_self_sig) != SC_OK) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "node signature rejected: node=%016llx timestamp=%llu", (unsigned long long)ni->node_id, (unsigned long long)ni->timestamp); return -1; } return 0; } /* Вычисляет суммарный размер динамической части NODEINFO по счётчикам заголовка. */ int topo_node_dyn_size(const struct TOPOMSG_NODE* msg) { return msg->node_name_len + msg->local_v4_sockets * sizeof(struct TOPOMSG_SOCKMETA4) + msg->local_v4_addrs * sizeof(struct TOPOMSG_ADDR4) + msg->local_v6_sockets * sizeof(struct TOPOMSG_SOCKMETA6) + msg->local_v6_addrs * sizeof(struct TOPOMSG_ADDR6) + msg->local_reality_socks * sizeof(struct TOPOMSG_REALITY_SOCK) + ((msg->flags & TOPO_FLAG_SEND_SUBNETS) ? (msg->local_v4_subnets * sizeof(struct TOPOMSG_SUBNET4) + msg->local_v6_subnets * sizeof(struct TOPOMSG_SUBNET6)) : 0) + msg->hop_count * 8; } /* Сериализует узел в wire-формат NODEINFO (заголовок + имя + адреса + подсети + hop_list). */ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq, uint64_t group_id, uint8_t flags, uint8_t* out, size_t out_max, uint16_t cumulative_rtt) { if (!ni || !nq || !out) return -1; struct TOPOMSG_NODE msg; memset(&msg, 0, sizeof(msg)); msg.flags = flags; msg.group_id = group_id; msg.node_id = ni->node_id; msg.ver = ni->ver; msg.timestamp = ni->timestamp; memcpy(msg.public_key, ni->public_key, SC_PUBKEY_SIZE); memcpy(msg.ed25519_public_key, ni->ed25519_public_key, SC_PUBKEY_SIZE); memcpy(msg.x25519_self_sig, ni->x25519_self_sig, 64); msg.node_name_len = ni->node_name ? (uint8_t)strlen(ni->node_name) : 0; if ((ni->node_name && strlen(ni->node_name) > 63) || topo_list_count((struct _topo_head*)ni->v4_sock_meta) > UINT8_MAX || topo_list_count((struct _topo_head*)ni->v4_addrs) > UINT8_MAX || topo_list_count((struct _topo_head*)ni->v6_sock_meta) > UINT8_MAX || topo_list_count((struct _topo_head*)ni->v6_addrs) > UINT8_MAX || topo_list_count((struct _topo_head*)ni->reality_socks) > UINT8_MAX || (nq->subnets && (topo_list_count((struct _topo_head*)nq->subnets->v4_subnets) > UINT8_MAX || topo_list_count((struct _topo_head*)nq->subnets->v6_subnets) > UINT8_MAX))) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node exceeds wire format limits"); return -1; } msg.local_v4_sockets = topo_list_count((struct _topo_head*)ni->v4_sock_meta); msg.local_v4_addrs = topo_list_count((struct _topo_head*)ni->v4_addrs); msg.local_v6_sockets = topo_list_count((struct _topo_head*)ni->v6_sock_meta); msg.local_v6_addrs = topo_list_count((struct _topo_head*)ni->v6_addrs); msg.local_reality_socks = topo_list_count((struct _topo_head*)ni->reality_socks); msg.local_v4_subnets = nq->subnets ? topo_list_count((struct _topo_head*)nq->subnets->v4_subnets) : 0; msg.local_v6_subnets = nq->subnets ? topo_list_count((struct _topo_head*)nq->subnets->v6_subnets) : 0; { uint8_t hc; uint64_t* hl = topo_node_best_hop_list(nq, &hc, NULL); msg.hop_count = hc; } msg.client_type = ni->client_type; msg.client_activity = ni->client_activity; msg.cumulative_rtt = cumulative_rtt; if (nq->radio) msg.flags |= TOPO_FLAG_RADIO; size_t total = TOPOMSG_NODE_HDR_SIZE + (size_t)topo_node_dyn_size(&msg); if (total > out_max) return -1; memcpy(out, &msg, TOPOMSG_NODE_HDR_SIZE); uint8_t* dp = out + TOPOMSG_NODE_HDR_SIZE; if (msg.node_name_len) { memcpy(dp, ni->node_name, msg.node_name_len); dp += msg.node_name_len; } { struct TOPO_SOCKMETA4* e = ni->v4_sock_meta; while (e) { struct TOPOMSG_SOCKMETA4 w = { .id=e->id, .config_type=e->config_type, .nat_type=e->nat_type }; memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next; }} { struct TOPO_ADDR4* e = ni->v4_addrs; while (e) { struct TOPOMSG_ADDR4 w = { .port=e->port, .type=e->type, .socket_id=e->socket_id, .protocol=e->protocol }; memcpy(w.addr, e->addr, 4); memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next; }} { struct TOPO_SOCKMETA6* e = ni->v6_sock_meta; while (e) { struct TOPOMSG_SOCKMETA6 w = { .id=e->id, .config_type=e->config_type, .nat_type=e->nat_type }; memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next; }} { struct TOPO_ADDR6* e = ni->v6_addrs; while (e) { struct TOPOMSG_ADDR6 w = { .port=e->port, .type=e->type, .socket_id=e->socket_id, .protocol=e->protocol }; memcpy(w.addr, e->addr, 16); memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next; }} { struct TOPO_REALITY_SOCK* e = ni->reality_socks; while (e) { struct TOPOMSG_REALITY_SOCK w; memset(&w, 0, sizeof(w)); w.socket_id = e->socket_id; memcpy(w.short_id, e->short_id, 8); memcpy(w.server_pubkey, e->server_pubkey, 32); memcpy(w.version, e->version, 3); memcpy(w.server_name, e->server_name, TOPO_REALITY_SNAME_MAX); memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next; }} if (msg.flags & TOPO_FLAG_SEND_SUBNETS) { if (nq->subnets) { { struct TOPO_SUBNET4* e = nq->subnets->v4_subnets; while (e) { struct TOPOMSG_SUBNET4 w = { .prefix_length=e->prefix_length }; memcpy(w.addr, e->addr, 4); memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next; }} { struct TOPO_SUBNET6* e = nq->subnets->v6_subnets; while (e) { struct TOPOMSG_SUBNET6 w = { .prefix_length=e->prefix_length }; memcpy(w.addr, e->addr, 16); memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next; }} } } { uint8_t hc; uint64_t* hl = topo_node_best_hop_list(nq, &hc, NULL); if (hc && hl) { size_t sz = hc * 8; memcpy(dp, hl, sz); dp += sz; } } return (int)(dp - out); } // ===== deserialization ===== /* Разбирает wire-формат NODEINFO в TOPO_NODE + подсети/hop_list/cumulative_rtt (память через пулы). */ int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t len, struct TOPO_NODE** out_ni, struct TOPO_NODESUBNETS** out_subnets, uint64_t** out_hop_list, uint8_t* out_hop_count, uint16_t* out_cumulative_rtt) { if (!group || !data || len < TOPOMSG_NODE_HDR_SIZE || !out_ni) return -1; const struct TOPOMSG_NODE* msg = (const struct TOPOMSG_NODE*)data; int dyn = topo_node_dyn_size(msg); if ((size_t)dyn + TOPOMSG_NODE_HDR_SIZE != len) return -1; struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc TOPO_NODE failed"); return -1; } ni->group_ref_count = 0; ni->node_id = msg->node_id; ni->ver = msg->ver; ni->timestamp = msg->timestamp; memcpy(ni->public_key, msg->public_key, SC_PUBKEY_SIZE); memcpy(ni->ed25519_public_key, msg->ed25519_public_key, SC_PUBKEY_SIZE); memcpy(ni->x25519_self_sig, msg->x25519_self_sig, 64); ni->client_type = msg->client_type; ni->client_activity = msg->client_activity; const uint8_t* dp = data + TOPOMSG_NODE_HDR_SIZE; if (msg->node_name_len) { ni->node_name = u_malloc(msg->node_name_len + 1); if (ni->node_name) { memcpy(ni->node_name, dp, msg->node_name_len); ni->node_name[msg->node_name_len] = 0; } dp += msg->node_name_len; } { struct TOPO_SOCKMETA4* head = NULL; struct TOPO_SOCKMETA4* tail = NULL; for (int i = 0; i < msg->local_v4_sockets; i++) { const struct TOPOMSG_SOCKMETA4* w = (const struct TOPOMSG_SOCKMETA4*)dp; dp += sizeof(*w); struct TOPO_SOCKMETA4* e = memory_pool_alloc(group->instance->topo_groups->v4_sock_meta_pool); if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; } e->id = w->id; e->config_type = w->config_type; e->nat_type = w->nat_type; e->next = NULL; if (!head) head = tail = e; else { tail->next = e; tail = e; } } ni->v4_sock_meta = head; } { struct TOPO_ADDR4* head = NULL; struct TOPO_ADDR4* tail = NULL; for (int i = 0; i < msg->local_v4_addrs; i++) { const struct TOPOMSG_ADDR4* w = (const struct TOPOMSG_ADDR4*)dp; dp += sizeof(*w); struct TOPO_ADDR4* e = memory_pool_alloc(group->instance->topo_groups->v4_addr_pool); if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; } memcpy(e->addr, w->addr, 4); e->port = w->port; e->type = w->type; e->socket_id = w->socket_id; e->protocol = w->protocol; e->next = NULL; if (!head) head = tail = e; else { tail->next = e; tail = e; } } ni->v4_addrs = head; } { struct TOPO_SOCKMETA6* head = NULL; struct TOPO_SOCKMETA6* tail = NULL; for (int i = 0; i < msg->local_v6_sockets; i++) { const struct TOPOMSG_SOCKMETA6* w = (const struct TOPOMSG_SOCKMETA6*)dp; dp += sizeof(*w); struct TOPO_SOCKMETA6* e = memory_pool_alloc(group->instance->topo_groups->v6_sock_meta_pool); if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; } e->id = w->id; e->config_type = w->config_type; e->nat_type = w->nat_type; e->next = NULL; if (!head) head = tail = e; else { tail->next = e; tail = e; } } ni->v6_sock_meta = head; } { struct TOPO_ADDR6* head = NULL; struct TOPO_ADDR6* tail = NULL; for (int i = 0; i < msg->local_v6_addrs; i++) { const struct TOPOMSG_ADDR6* w = (const struct TOPOMSG_ADDR6*)dp; dp += sizeof(*w); struct TOPO_ADDR6* e = memory_pool_alloc(group->instance->topo_groups->v6_addr_pool); if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; } memcpy(e->addr, w->addr, 16); e->port = w->port; e->type = w->type; e->socket_id = w->socket_id; e->protocol = w->protocol; e->next = NULL; if (!head) head = tail = e; else { tail->next = e; tail = e; } } ni->v6_addrs = head; } { struct TOPO_REALITY_SOCK* head = NULL; struct TOPO_REALITY_SOCK* tail = NULL; for (int i = 0; i < msg->local_reality_socks; i++) { const struct TOPOMSG_REALITY_SOCK* w = (const struct TOPOMSG_REALITY_SOCK*)dp; dp += sizeof(*w); struct TOPO_REALITY_SOCK* e = u_calloc(1, sizeof(struct TOPO_REALITY_SOCK)); if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; } e->socket_id = w->socket_id; memcpy(e->short_id, w->short_id, 8); memcpy(e->server_pubkey, w->server_pubkey, 32); memcpy(e->version, w->version, 3); memcpy(e->server_name, w->server_name, TOPO_REALITY_SNAME_MAX); e->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0; e->next = NULL; if (!head) head = tail = e; else { tail->next = e; tail = e; } } ni->reality_socks = head; } if (out_subnets && (msg->flags & TOPO_FLAG_SEND_SUBNETS) && (msg->local_v4_subnets || msg->local_v6_subnets)) { struct TOPO_NODESUBNETS* r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS)); if (!r) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; } for (int i = 0; i < msg->local_v4_subnets; i++) { const struct TOPOMSG_SUBNET4* w = (const struct TOPOMSG_SUBNET4*)dp; dp += sizeof(*w); struct TOPO_SUBNET4* e = memory_pool_alloc(group->instance->topo_groups->v4_subnet_pool); if (!e) { u_free(r); topo_node_free_raw(group->instance->topo_groups, ni); return -1; } memcpy(e->addr, w->addr, 4); e->prefix_length = w->prefix_length; e->next = r->v4_subnets; r->v4_subnets = e; } for (int i = 0; i < msg->local_v6_subnets; i++) { const struct TOPOMSG_SUBNET6* w = (const struct TOPOMSG_SUBNET6*)dp; dp += sizeof(*w); struct TOPO_SUBNET6* e = memory_pool_alloc(group->instance->topo_groups->v6_subnet_pool); if (!e) { u_free(r); topo_node_free_raw(group->instance->topo_groups, ni); return -1; } memcpy(e->addr, w->addr, 16); e->prefix_length = w->prefix_length; e->next = r->v6_subnets; r->v6_subnets = e; } *out_subnets = r; } else if (!(msg->flags & TOPO_FLAG_SEND_SUBNETS)) { dp += msg->local_v4_subnets * sizeof(struct TOPOMSG_SUBNET4) + msg->local_v6_subnets * sizeof(struct TOPOMSG_SUBNET6); if (out_subnets) *out_subnets = NULL; } else { if (out_subnets) *out_subnets = NULL; } if (out_hop_list && msg->hop_count > 0) { size_t hsz = msg->hop_count * 8; *out_hop_list = u_malloc(hsz); if (!*out_hop_list) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; } memcpy(*out_hop_list, dp, hsz); dp += hsz; *out_hop_count = msg->hop_count; } if (out_cumulative_rtt) *out_cumulative_rtt = msg->cumulative_rtt; *out_ni = ni; return 0; } /* Ищет per-group узел TOPO_GROUP_NODE в группе по node_id (NULL, если нет). */ struct TOPO_GROUP_NODE* topo_node_find_by_id(struct TOPO_GROUP* group, uint64_t node_id) { if (!group || !group->nodes) return NULL; uint64_t key = node_id; struct ll_entry* e = queue_find_data_by_index(group->nodes, &key); return e ? (struct TOPO_GROUP_NODE*)e : NULL; } /* Решает, нужен ли ping узлу: да, если в любой группе last_ping старше PING_TTL_TB. */ int topo_node_ping_request_cbk(struct TOPO_GROUPS* groups, uint64_t node_id) { if (!groups || !groups->group_list) return 0; uint64_t now = get_time_tb(); struct ll_entry* ge = groups->group_list->head; while (ge) { struct TOPO_GROUP* g = (struct TOPO_GROUP*)ge; struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g, node_id); if (nq) { struct TOPO_CONNECTIVITY* c = &nq->connectivity; if (c->last_ping_time == 0 || (now - c->last_ping_time) > PING_TTL_TB) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ping_request_cbk: node=%016llx last_ping=%llu now=%llu stale=1", (unsigned long long)node_id, (unsigned long long)c->last_ping_time, (unsigned long long)now); return 1; } DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ping_request_cbk: node=%016llx last_ping=%llu now=%llu stale=0", (unsigned long long)node_id, (unsigned long long)c->last_ping_time, (unsigned long long)now); } ge = ge->next; } return 0; } /* Обновляет RTT/last_ping узла во всех группах, пишет RTT в БД и уведомляет подписчиков. */ void topo_node_ping_update_rtt(struct TOPO_GROUPS* groups, uint64_t node_id, uint16_t rtt) { if (!groups || !groups->group_list) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "ping_update_rtt: no groups, node=%016llx rtt=%u", (unsigned long long)node_id, (unsigned)rtt); return; } uint64_t now = get_time_tb(); struct ll_entry* ge = groups->group_list->head; while (ge) { struct TOPO_GROUP* g = (struct TOPO_GROUP*)ge; struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g, node_id); if (nq) { nq->connectivity.last_ping_time = now; if (nq->connectivity.interface_min_rtt == 0 || rtt < nq->connectivity.interface_min_rtt) nq->connectivity.interface_min_rtt = rtt; if (nq->connectivity.nat_min_rtt == 0 || rtt < nq->connectivity.nat_min_rtt) nq->connectivity.nat_min_rtt = rtt; if (nq->connectivity.real_min_rtt == 0 || rtt < nq->connectivity.real_min_rtt) nq->connectivity.real_min_rtt = rtt; DEBUG_TRACE(DEBUG_CATEGORY_BGP, "ping_update_rtt: node=%016llx rtt=%u (%llu) group=%016llx", (unsigned long long)node_id, (unsigned)rtt, (unsigned long long)now, (unsigned long long)g->group_id); if (groups->instance && groups->instance->topo_sqlite_db) topo_node_sqlite_update_rtt(groups->instance->topo_sqlite_db, node_id, rtt); topo_fire_nodeinfo_cbk(groups->instance, g, nq); } ge = ge->next; } } /* Суммарный RTT цепочки до узла: минимальный (rtt_last + cumulative_rtt) среди живых путей. */ uint16_t topo_get_chain_rtt(struct TOPO_GROUP_NODE* nq) { if (!nq || !nq->paths || !nq->paths->head) { return 0xFFFF; } uint16_t best = 0xFFFF; for (struct ll_entry* e = nq->paths->head; e; e = e->next) { struct TOPO_NODEPATH* p = (struct TOPO_NODEPATH*)e; if (!p->conn || !p->conn->links_up || !p->conn->rtt_last) { continue; } uint16_t total = (uint16_t)((uint32_t)p->conn->rtt_last + p->cumulative_rtt); if (total < best) best = total; } return best; } // ===== dump / format ===== /* Есть ли у узла непустая диагностика связности (статусы/таймстемпы проб). */ static int is_node_connectivity_active(struct TOPO_CONNECTIVITY* c) { return c->probe_status != PROBE_STATUS_NONE || c->interface_status != PROBE_RESULT_UNKNOWN || c->nat_status != PROBE_RESULT_UNKNOWN || c->real_status != PROBE_RESULT_UNKNOWN || c->probe_start_time != 0 || c->ping_req_time != 0 || c->last_ping_time != 0; } /* Читаемое имя статуса/результата пробы и типа conn_mgr для дампа. */ static const char* probe_status_str(uint8_t s) { switch (s) { case 0: return "NONE"; case 1: return "IN_PROGRESS"; case 2: return "DONE"; default: return "?"; } } static const char* probe_result_str(uint8_t s) { switch (s) { case 0: return "UNKNOWN"; case 1: return "REACHABLE"; case 2: return "UNREACHABLE"; default: return "?"; } } static const char* conn_mgr_type_str(uint8_t t) { switch (t) { case 1: return "DIRECT"; case 2: return "REVERSE"; case 3: return "INDIRECT"; default: return "NONE"; } } /* Выводит в лог полный дамп узлов группы (адреса, пути, связность, conn_mgr). */ void topo_node_dump_all(struct TOPO_GROUP* group) { if (!group || !group->nodes) return; DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== BGP Nodes dump: %d nodes ===", queue_entry_count(group->nodes)); int idx = 0; struct ll_entry* e = group->nodes->head; while (e) { struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e; struct TOPO_NODE* ni = topo_node_registry_find(group->instance->topo_groups, nq->node_id); if (!ni) { e = e->next; continue; } int is_local = (group->local_node == nq); const char* name_s = ni->node_name ? ni->node_name : ""; DEBUG_INFO(DEBUG_CATEGORY_BGP, "--- Node #%d: id=%016llx name=\"%s\" ver=%u grp=%llu%s ---", idx, (unsigned long long)ni->node_id, name_s, ni->ver, (unsigned long long)group->group_id, is_local ? " [LOCAL]" : ""); log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " pubkey ", ni->public_key, SC_PUBKEY_SIZE); log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " ed25519", ni->ed25519_public_key, SC_PUBKEY_SIZE); int si = 0; { struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; while (sm) { DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sock[%d]: id=%u config=%u nat=%u", si++, sm->id, sm->config_type, sm->nat_type); sm = sm->next; }} { const char* atn[] = {"INTERFACE","NAT","REAL"}; int ai = 0; struct TOPO_ADDR4* a = ni->v4_addrs; while (a) { const char* proto = (a->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a->protocol & TOPO_PROTO_TCP) ? "tcp" : (a->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4addr[%d]: %s:%u %s sock=%u proto=%s", ai++, ip_to_str(a->addr, AF_INET).str, a->port, (a->type<=2)?atn[a->type]:"?", a->socket_id, proto); a = a->next; }} { int si6 = 0; struct TOPO_SOCKMETA6* sm6 = ni->v6_sock_meta; while (sm6) { DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sock[%d]: id=%u config=%u nat=%u", si6++, sm6->id, sm6->config_type, sm6->nat_type); sm6 = sm6->next; }} { int ai6 = 0; struct TOPO_ADDR6* a6 = ni->v6_addrs; while (a6) { const char* proto = (a6->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a6->protocol & TOPO_PROTO_TCP) ? "tcp" : (a6->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6addr[%d]: %s:%u type=%u sock=%u proto=%s", ai6++, ip_to_str(a6->addr, AF_INET6).str, a6->port, a6->type, a6->socket_id, proto); a6 = a6->next; }} if (nq->subnets) { { int ri = 0; struct TOPO_SUBNET4* sub = nq->subnets->v4_subnets; while (sub) { DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sub[%d]: %s/%d", ri++, ip_to_str(sub->addr, AF_INET).str, sub->prefix_length); sub = sub->next; }} { int ri6 = 0; struct TOPO_SUBNET6* sub6 = nq->subnets->v6_subnets; while (sub6) { DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sub[%d]: %s/%d", ri6++, ip_to_str(sub6->addr, AF_INET6).str, sub6->prefix_length); sub6 = sub6->next; }} } int path_idx = 0; if (nq->paths) { struct ll_entry* pe = nq->paths->head; while (pe) { struct TOPO_NODEPATH* path = (struct TOPO_NODEPATH*)pe; uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct TOPO_NODEPATH)); char hop_buf[256] = ""; int hop_pos = 0; for (uint8_t hi = 0; hi < path->hop_count && hop_pos < (int)sizeof(hop_buf)-20; hi++) hop_pos += snprintf(hop_buf + hop_pos, sizeof(hop_buf) - hop_pos, "%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]); DEBUG_INFO(DEBUG_CATEGORY_BGP, " path[%d]: conn=%s hop_count=%u hops=[%s]", path_idx, path->conn ? path->conn->log_name : "NULL", path->hop_count, hop_buf); path_idx++; pe = pe->next; } } struct TOPO_CONNECTIVITY* c = &nq->connectivity; if (is_node_connectivity_active(c)) { DEBUG_INFO(DEBUG_CATEGORY_BGP, " connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu last_ping_tb=%llu", probe_status_str(c->probe_status), probe_result_str(c->interface_status), c->interface_min_rtt, probe_result_str(c->nat_status), c->nat_min_rtt, probe_result_str(c->real_status), c->real_min_rtt, c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt, (unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time, (unsigned long long)c->last_ping_time); } if (nq->conn_mgr_type != CONN_TYPE_NONE) { char interm_buf[200] = ""; int ipos = 0; for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count && ipos < (int)sizeof(interm_buf)-19; i++) ipos += snprintf(interm_buf + ipos, sizeof(interm_buf) - ipos, "%s%016llx", i ? "," : "", (unsigned long long)nq->conn_mgr_intermediaries[i]); DEBUG_INFO(DEBUG_CATEGORY_BGP, " conn_mgr: type=%s intermediates=[%s]", conn_mgr_type_str(nq->conn_mgr_type), interm_buf); } e = e->next; idx++; } } /* То же, что dump_all, но в строковый буфер (для control-сервера). */ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) { if (!group || !group->nodes || !buf || buf_size == 0) return 0; int pos = 0; pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "=== BGP Nodes dump: %d nodes ===\n", queue_entry_count(group->nodes)); int idx = 0; struct ll_entry* e = group->nodes->head; while (e && (size_t)pos < buf_size) { struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e; struct TOPO_NODE* ni = topo_node_registry_find(group->instance->topo_groups, nq->node_id); if (!ni) { e = e->next; continue; } int is_local = (group->local_node == nq); const char* name_s = ni->node_name ? ni->node_name : ""; #define FMT_ADD(fmt, ...) pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, fmt, ##__VA_ARGS__) FMT_ADD("--- Node #%d: id=%016llx name=\"%s\" ver=%u grp=%llu%s ---\n", idx, (unsigned long long)ni->node_id, name_s, ni->ver, (unsigned long long)group->group_id, is_local ? " [LOCAL]" : ""); FMT_ADD(" pubkey: "); for (int k = 0; k < SC_PUBKEY_SIZE; k++) FMT_ADD("%02x", ni->public_key[k]); FMT_ADD("\n"); FMT_ADD(" ed25519: "); for (int k = 0; k < SC_PUBKEY_SIZE; k++) FMT_ADD("%02x", ni->ed25519_public_key[k]); FMT_ADD("\n"); { struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; int si = 0; while (sm) { FMT_ADD(" v4sock[%d]: id=%u config=%u nat=%u\n", si++, sm->id, sm->config_type, sm->nat_type); sm = sm->next; }} { const char* atn[] = {"INTERFACE","NAT","REAL"}; struct TOPO_ADDR4* a = ni->v4_addrs; int ai = 0; while (a) { const char* proto = (a->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a->protocol & TOPO_PROTO_TCP) ? "tcp" : (a->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; FMT_ADD(" v4addr[%d]: %s:%u %s sock=%u proto=%s\n", ai++, ip_to_str(a->addr, AF_INET).str, a->port, (a->type<=2)?atn[a->type]:"?", a->socket_id, proto); a = a->next; }} { struct TOPO_SOCKMETA6* sm6 = ni->v6_sock_meta; int s6i = 0; while (sm6) { FMT_ADD(" v6sock[%d]: id=%u config=%u nat=%u\n", s6i++, sm6->id, sm6->config_type, sm6->nat_type); sm6 = sm6->next; }} { struct TOPO_ADDR6* a6 = ni->v6_addrs; int a6i = 0; while (a6) { const char* proto = (a6->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a6->protocol & TOPO_PROTO_TCP) ? "tcp" : (a6->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; FMT_ADD(" v6addr[%d]: %s:%u type=%u sock=%u proto=%s\n", a6i++, ip_to_str(a6->addr, AF_INET6).str, a6->port, a6->type, a6->socket_id, proto); a6 = a6->next; }} if (nq->subnets) { { struct TOPO_SUBNET4* sub = nq->subnets->v4_subnets; int ri = 0; while (sub) { FMT_ADD(" v4sub[%d]: %s/%d\n", ri++, ip_to_str(sub->addr, AF_INET).str, sub->prefix_length); sub = sub->next; }} { struct TOPO_SUBNET6* sub6 = nq->subnets->v6_subnets; int r6i = 0; while (sub6) { FMT_ADD(" v6sub[%d]: %s/%d\n", r6i++, ip_to_str(sub6->addr, AF_INET6).str, sub6->prefix_length); sub6 = sub6->next; }} } if (nq->paths) { struct ll_entry* pe = nq->paths->head; int pi = 0; while (pe) { struct TOPO_NODEPATH* path = (struct TOPO_NODEPATH*)pe; uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct TOPO_NODEPATH)); FMT_ADD(" path[%d]: conn=%s hop_count=%u hops=[", pi++, path->conn ? path->conn->log_name : "NULL", path->hop_count); for (uint8_t hi = 0; hi < path->hop_count; hi++) FMT_ADD("%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]); FMT_ADD("]\n"); pe = pe->next; } } struct TOPO_CONNECTIVITY* c = &nq->connectivity; if (is_node_connectivity_active(c)) { FMT_ADD(" connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu last_ping_tb=%llu\n", probe_status_str(c->probe_status), probe_result_str(c->interface_status), c->interface_min_rtt, probe_result_str(c->nat_status), c->nat_min_rtt, probe_result_str(c->real_status), c->real_min_rtt, c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt, (unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time, (unsigned long long)c->last_ping_time); } if (nq->conn_mgr_type != CONN_TYPE_NONE) { FMT_ADD(" conn_mgr: type=%s intermediates=[", conn_mgr_type_str(nq->conn_mgr_type)); for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) FMT_ADD("%s%016llx", i ? "," : "", (unsigned long long)nq->conn_mgr_intermediaries[i]); FMT_ADD("]\n"); } #undef FMT_ADD e = e->next; idx++; } if ((size_t)pos >= buf_size) { pos = (int)buf_size - 14; if (pos < 0) pos = 0; pos += snprintf(buf + pos, buf_size - pos, "\n[TRUNCATED]\n"); } return pos; } // ===== update my nodeinfo ===== /* Пересоздаёт local_node группы при изменении имени/подсетей/ключей, инкрементируя версию. */ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group) { if (!instance || !group) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; } size_t name_len = 0; if (instance->name[0]) { name_len = strlen(instance->name); if (name_len > 63) name_len = 63; } int vc = 0, vc6 = 0; struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets; while (s) { if (s->ip.family == AF_INET) vc++; else if (s->ip.family == AF_INET6) vc6++; s = s->next; } // Для chat групп подсети не передаём if (group->group_type == TOPO_GROUP_TYPE_CHAT) { vc = 0; vc6 = 0; } int changed = 1; uint8_t old_ver = 0; if (group->local_node) { struct TOPO_NODE* oni = topo_node_registry_find(instance->topo_groups, group->local_node->node_id); if (oni) { old_ver = oni->ver; changed = (vc != (group->local_node->subnets ? topo_list_count((struct _topo_head*)group->local_node->subnets->v4_subnets) : 0)) || (name_len != (oni->node_name ? strlen(oni->node_name) : 0)) || (name_len && strncmp(instance->name, oni->node_name ? oni->node_name : "", name_len)) || (memcmp(oni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE) != 0) || (vc6 != (group->local_node->subnets ? topo_list_count((struct _topo_head*)group->local_node->subnets->v6_subnets) : 0)) || (oni->client_type != instance->client_type) || (oni->client_activity != instance->client_activity) || (group->local_node->radio != group->radio_active) || (memcmp(oni->ed25519_public_key, group->ed25519_public_key, SC_PUBKEY_SIZE) != 0); } } if (!changed && group->local_node) DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "my nodeinfo unchanged (identity), ver=%d grp=%016llx", group->local_node->last_ver, (unsigned long long)group->group_id); if (changed) { struct TOPO_NODE* ni = topo_node_registry_find(instance->topo_groups, instance->node_id); if (ni) topo_node_ref(ni); /* новая группа удерживает запись до освобождения старой */ if (group->local_node) { if (instance->rt) route_delete(instance->rt, group->local_node); topo_nodeq_free_group_fields(instance->topo_groups, group->local_node); u_free(group->local_node); group->local_node = NULL; } group->local_node = u_calloc(1, sizeof(struct TOPO_GROUP_NODE)); if (!group->local_node) { if (ni) topo_node_registry_unref(instance->topo_groups, instance->node_id); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local group node allocation failed"); return -1; } struct TOPO_GROUP_NODE* lq = group->local_node; int new_identity = ni == NULL; if (!ni) ni = u_calloc(1, sizeof(struct TOPO_NODE)); if (!ni) { u_free(lq); group->local_node = NULL; return -1; } struct TOPO_NODE old_identity = *ni; ni->node_id = instance->node_id; ni->ver = (old_ver % 255) + 1; ni->client_type = instance->client_type; ni->client_activity = instance->client_activity; lq->radio = group->radio_active; memcpy(ni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE); memcpy(ni->ed25519_public_key, group->ed25519_public_key, SC_PUBKEY_SIZE); ni->node_name = u_strdup(instance->name); if (!ni->node_name || topo_node_sign_self(instance, ni) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot publish local identity"); u_free(ni->node_name); *ni = old_identity; if (new_identity) topo_node_free_raw(instance->topo_groups, ni); else topo_node_registry_unref(instance->topo_groups, instance->node_id); u_free(lq); group->local_node = NULL; return -1; } u_free(old_identity.node_name); if (new_identity) ni = topo_node_registry_store(instance->topo_groups, ni); if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot register local identity"); return -1; } lq->node_id = ni->node_id; lq->last_ver = ni->ver; lq->last_timestamp = ni->timestamp; if (group->group_type != TOPO_GROUP_TYPE_CHAT && (vc || vc6)) { struct TOPO_NODESUBNETS* r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS)); s = instance->config->my_subnets; while (s) { if (s->ip.family == AF_INET) { struct TOPO_SUBNET4* sub = memory_pool_alloc(group->instance->topo_groups->v4_subnet_pool); memcpy(sub->addr, &s->ip.addr.v4, 4); sub->prefix_length = s->netmask; sub->next = r->v4_subnets; r->v4_subnets = sub; } else if (s->ip.family == AF_INET6) { struct TOPO_SUBNET6* sub6 = memory_pool_alloc(group->instance->topo_groups->v6_subnet_pool); memcpy(sub6->addr, &s->ip.addr.v6, 16); sub6->prefix_length = s->netmask; sub6->next = r->v6_subnets; r->v6_subnets = sub6; } s = s->next; } lq->subnets = r; } if (group->group_type == TOPO_GROUP_TYPE_CHAT) DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4sub=%d v6sub=%d ver=%d grp=%016llx", vc, vc6, ni->ver, (unsigned long long)group->group_id); else DEBUG_INFO(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4sub=%d v6sub=%d ver=%d grp=%016llx", vc, vc6, ni->ver, (unsigned long long)group->group_id); if (group->group_type != TOPO_GROUP_TYPE_CHAT && instance->rt && !route_insert(instance->rt, lq)) DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "failed to insert local routes"); } else { struct TOPO_NODE* lni = topo_node_registry_find(instance->topo_groups, group->local_node->node_id); if (lni) group->local_node->last_ver = lni->ver; } return vc; } /* Пересобирает списки адресов своего узла из ETCP-сокетов; при изменении — подпись, БД, рассылка. */ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) { if (!instance || !instance->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; } struct TOPO_GROUP* default_group = topo_groups_get_default(instance->topo_groups); if (!default_group || !default_group->local_node) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "no local_node"); return -1; } struct TOPO_NODE* ni = topo_node_registry_find(instance->topo_groups, instance->node_id); if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "my node not in registry"); return -1; } /* Собираем новые списки во временные головы, чтобы сравнить со старыми до их освобождения. */ struct TOPO_SOCKMETA4* m4_head = NULL; struct TOPO_ADDR4* v4_head = NULL; struct TOPO_SOCKMETA6* m6_head = NULL; struct TOPO_ADDR6* v6_head = NULL; struct TOPO_REALITY_SOCK* r_head = NULL; /* node-global reality-параметры (одинаковы для всех reality-сокетов): рекламируем первый short_id */ uint8_t r_short_id[8], r_pubkey[32], r_version[3]; char r_sname[TOPO_REALITY_SNAME_MAX]; uint8_t r_valid = 0; { const struct reality_config* rc = &instance->config->global.reality; if (rc->enabled && rc->has_private_key) { if (reality_pubkey_from_priv(rc->private_key, r_pubkey) == REALITY_OK) { if (rc->short_id_count > 0) memcpy(r_short_id, rc->short_ids[0], 8); else memcpy(r_short_id, rc->short_id, 8); memcpy(r_version, rc->version, 3); memset(r_sname, 0, sizeof(r_sname)); strncpy(r_sname, rc->server_name, sizeof(r_sname) - 1); r_valid = 1; } } } struct ETCP_SOCKET* e_sock = instance->etcp_sockets; while (e_sock) { if (e_sock->is_tcp) { e_sock = e_sock->next; continue; } if (e_sock->type == CFG_SERVER_TYPE_PRIVATE) { e_sock = e_sock->next; continue; } /* private скрыт, LOCAL рекламируем (LAN) */ if (e_sock->local_addr.ss_family == AF_INET) { { struct TOPO_SOCKMETA4* sm = memory_pool_alloc(instance->topo_groups->v4_sock_meta_pool); sm->id = e_sock->sock_id; sm->config_type = e_sock->type; sm->nat_type = e_sock->nat_type; sm->next = m4_head; m4_head = sm; } struct sockaddr_in* local_sin = (struct sockaddr_in*)&e_sock->local_addr; struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr; struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr; /* адрес ДО NAT (локальный/интерфейсный) — рекламируем всегда */ int use_local = (local_sin->sin_addr.s_addr != 0); uint32_t pre_ip = use_local ? local_sin->sin_addr.s_addr : if_sin->sin_addr.s_addr; uint16_t pre_port = ntohs(use_local ? local_sin->sin_port : if_sin->sin_port); { struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool); memcpy(a->addr, &pre_ip, 4); a->port = pre_port; a->type = TOPO_ADDR_INTERFACE; a->socket_id = e_sock->sock_id; a->protocol = TOPO_PROTO_UDP; a->next = v4_head; v4_head = a; } /* адрес ПОСЛЕ NAT — дополнительно, если NAT определён (EIM) и есть интернет. DIRECT (nat==интерфейс), STRICT и оффлайн не рекламируем отдельным адресом. */ if (instance->ntp.reachable && e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0 && e_sock->nat_type != NAT_VERIFIED_STRICT && e_sock->nat_type != NAT_VERIFIED_DIRECT) { struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool); memcpy(a->addr, &nat_sin->sin_addr.s_addr, 4); a->port = ntohs(nat_sin->sin_port); a->type = TOPO_ADDR_NAT; a->socket_id = e_sock->sock_id; a->protocol = TOPO_PROTO_UDP; a->next = v4_head; v4_head = a; } } else if (e_sock->local_addr.ss_family == AF_INET6) { { struct TOPO_SOCKMETA6* sm6 = memory_pool_alloc(instance->topo_groups->v6_sock_meta_pool); sm6->id = e_sock->sock_id; sm6->config_type = e_sock->type; sm6->nat_type = e_sock->nat_type; sm6->next = m6_head; m6_head = sm6; } struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr; { struct TOPO_ADDR6* a6 = memory_pool_alloc(instance->topo_groups->v6_addr_pool); memcpy(a6->addr, &if_sin6->sin6_addr, 16); a6->port = ntohs(if_sin6->sin6_port); a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = e_sock->sock_id; a6->protocol = TOPO_PROTO_UDP; a6->next = v6_head; v6_head = a6; } } e_sock = e_sock->next; } { struct ETCP_SOCKET* ts = instance->etcp_sockets; while (ts) { if (!ts->is_tcp) { ts = ts->next; continue; } if (ts->type == CFG_SERVER_TYPE_PRIVATE) { ts = ts->next; continue; } /* private скрыт, LOCAL рекламируем (LAN) */ struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr; if (!addr || !addr->ss_family) { ts = ts->next; continue; } const int is_reality = r_valid && ts->reality_enabled; uint8_t proto = TOPO_PROTO_TCP | (is_reality ? TOPO_PROTO_REALITY : 0); if (addr->ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)addr; { struct TOPO_SOCKMETA4* sm = memory_pool_alloc(instance->topo_groups->v4_sock_meta_pool); sm->id = ts->sock_id; sm->config_type = ts->type; sm->nat_type = ts->nat_type; sm->next = m4_head; m4_head = sm; } { struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool); memcpy(a->addr, &sin->sin_addr.s_addr, 4); a->port = ntohs(sin->sin_port); a->type = TOPO_ADDR_INTERFACE; a->socket_id = ts->sock_id; a->protocol = proto; a->next = v4_head; v4_head = a; } } else if (addr->ss_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr; { struct TOPO_SOCKMETA6* sm6 = memory_pool_alloc(instance->topo_groups->v6_sock_meta_pool); sm6->id = ts->sock_id; sm6->config_type = ts->type; sm6->nat_type = ts->nat_type; sm6->next = m6_head; m6_head = sm6; } { struct TOPO_ADDR6* a6 = memory_pool_alloc(instance->topo_groups->v6_addr_pool); memcpy(a6->addr, &sin6->sin6_addr, 16); a6->port = ntohs(sin6->sin6_port); a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = ts->sock_id; a6->protocol = proto; a6->next = v6_head; v6_head = a6; } } if (is_reality) { struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(struct TOPO_REALITY_SOCK)); if (!r) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "reality_sock alloc failed"); } else { r->socket_id = ts->sock_id; memcpy(r->short_id, r_short_id, 8); memcpy(r->server_pubkey, r_pubkey, 32); memcpy(r->version, r_version, 3); memcpy(r->server_name, r_sname, TOPO_REALITY_SNAME_MAX); r->next = r_head; r_head = r; } } ts = ts->next; } } /* Без изменений — подпись/рассылку гасим, но БД всё равно синхронизируем: она могла быть не готова (или пуста) при предыдущем вызове. */ int changed = !(sockmeta4_list_equal(ni->v4_sock_meta, m4_head) && addr4_list_equal(ni->v4_addrs, v4_head) && sockmeta6_list_equal(ni->v6_sock_meta, m6_head) && addr6_list_equal(ni->v6_addrs, v6_head) && reality_sock_list_equal(ni->reality_socks, r_head)); if (changed) { struct TOPO_NODE candidate = *ni; candidate.v4_sock_meta = m4_head; candidate.v4_addrs = v4_head; candidate.v6_sock_meta = m6_head; candidate.v6_addrs = v6_head; candidate.reality_socks = r_head; candidate.ver = (ni->ver % 255) + 1; if (topo_node_sign_self(instance, &candidate) < 0) { free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, m4_head); free_v4_addr_list(instance->topo_groups->v4_addr_pool, v4_head); free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, m6_head); free_v6_addr_list(instance->topo_groups->v6_addr_pool, v6_head); free_reality_sock_list(r_head); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address snapshot publication failed"); return -1; } free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, ni->v4_sock_meta); ni->v4_sock_meta = m4_head; free_v4_addr_list(instance->topo_groups->v4_addr_pool, ni->v4_addrs); ni->v4_addrs = v4_head; free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, ni->v6_sock_meta); ni->v6_sock_meta = m6_head; free_v6_addr_list(instance->topo_groups->v6_addr_pool, ni->v6_addrs); ni->v6_addrs = v6_head; free_reality_sock_list(ni->reality_socks); ni->reality_socks = r_head; ni->ver = candidate.ver; ni->timestamp = candidate.timestamp; memcpy(ni->x25519_self_sig, candidate.x25519_self_sig, sizeof(ni->x25519_self_sig)); DEBUG_INFO(DEBUG_CATEGORY_BGP, "my addresses updated, new ver=%d", ni->ver); } else { free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, m4_head); free_v4_addr_list(instance->topo_groups->v4_addr_pool, v4_head); free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, m6_head); free_v6_addr_list(instance->topo_groups->v6_addr_pool, v6_head); free_reality_sock_list(r_head); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "my addresses unchanged (ver=%d), persist DB", ni->ver); } /* всегда: адреса (addrs_put) + классификация node_type/storage (nodeinfo_updated) */ if (instance->topo_sqlite_db) { time_t now_sec = ntp_time_get_seconds(instance); topo_node_sqlite_node_update_verified(instance->topo_sqlite_db, instance->node_id, instance->name, instance->my_keys.public_key, instance->my_ed25519_pubkey, (uint64_t)now_sec, now_sec); topo_node_sqlite_addrs_put(instance->topo_sqlite_db, instance->node_id, ni); topo_node_sqlite_nodeinfo_updated(instance->topo_sqlite_db, instance->node_id); } if (changed) { struct ll_entry* ge = instance->topo_groups->group_list->head; while (ge) { struct TOPO_GROUP* g = (struct TOPO_GROUP*)ge; if (g->local_node) { 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, 0); se = se->next; } } } ge = ge->next; } } return changed ? 1 : 0; } /* Callback изменения сокета (адрес/статус) — инициирует пересборку адресов своего узла. */ void topo_node_on_socket_changed(struct ETCP_SOCKET* sock, int event, void* arg) { (void)arg; if (!sock || !sock->instance) return; DEBUG_INFO(DEBUG_CATEGORY_BGP, "socket %s changed (event=0x%x), updating nodeinfo", sock->name, event); topo_node_update_my_addresses(sock->instance); }