diff --git a/src/routing_layer/topo_node.c b/src/routing_layer/topo_node.c index 5f4b50a9..d4765563 100644 --- a/src/routing_layer/topo_node.c +++ b/src/routing_layer/topo_node.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "../lib/ll_queue.h" #include "../lib/debug_config.h" @@ -971,23 +972,36 @@ void topo_node_dump_all(struct TOPO_GROUP* group) { /* То же, что 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; + if (buf && buf_size) buf[0] = '\0'; + if (!group || !group->nodes || !buf || buf_size == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node dump: invalid arguments group=%p buf=%p capacity=%zu", (void*)group, (void*)buf, buf_size); + return 0; + } + if (buf_size > INT_MAX) buf_size = INT_MAX; + size_t 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)); +#define FMT_ADD(...) do { \ + int added = snprintf(buf + pos, buf_size - pos, __VA_ARGS__); \ + if (added < 0) { \ + buf[pos] = '\0'; \ + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node dump: formatting failed after %zu bytes", pos); \ + return -1; \ + } \ + if ((size_t)added >= buf_size - pos) { pos = buf_size - 1; goto truncated; } \ + pos += (size_t)added; \ +} while (0) + + FMT_ADD("=== 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) { + 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 : ""; -#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]" : ""); @@ -1052,15 +1066,24 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) { FMT_ADD("]\n"); } -#undef FMT_ADD e = e->next; idx++; } +#undef FMT_ADD - 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"); + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node dump: group=%016llx nodes=%d written=%zu capacity=%zu", + (unsigned long long)group->group_id, idx, pos, buf_size); + return (int)pos; + +truncated: { + const char marker[] = "\n[TRUNCATED]\n"; + size_t marker_len = sizeof(marker) - 1; + if (marker_len > pos) marker_len = pos; + memcpy(buf + pos - marker_len, marker, marker_len); + buf[pos] = '\0'; + DEBUG_WARN(DEBUG_CATEGORY_BGP, "node dump truncated: group=%016llx written=%zu capacity=%zu", + (unsigned long long)group->group_id, pos, buf_size); + return (int)pos; } - return pos; } // ===== update my nodeinfo ===== diff --git a/src/routing_layer/topo_node.h b/src/routing_layer/topo_node.h index 91c61557..31bebbda 100644 --- a/src/routing_layer/topo_node.h +++ b/src/routing_layer/topo_node.h @@ -278,8 +278,11 @@ int topo_node_init_self(struct UTUN_INSTANCE* instance); int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance); /* 1=changed, 0=unchanged, -1=error */ /* Обработчик изменений локального сокета: обновляет адреса узла. */ void topo_node_on_socket_changed(struct ETCP_SOCKET* sock, int event, void* arg); -/* Вывести диагностику узлов группы в лог / текстовый буфер. */ +/* Вывести диагностику узлов группы в лог категории BGP на уровне INFO. */ void topo_node_dump_all(struct TOPO_GROUP* group); +/* Записать дамп в buf: длина без NUL или -1 при ошибке форматирования. Ёмкость ограничена INT_MAX. + * При усечении завершает запись сразу, заменяет хвост доступной частью "\n[TRUNCATED]\n" и возвращает реальную длину. + * При buf != NULL и buf_size > 0 строка всегда завершена NUL. Некорректные аргументы дают 0 и пустую строку, если есть буфер. */ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size); /* 1, если хотя бы в одной группе пора повторить ping этого узла; иначе 0. */ int topo_node_ping_request_cbk(struct TOPO_GROUPS* groups, uint64_t node_id); diff --git a/tests/test_node_snapshot.c b/tests/test_node_snapshot.c index 2599532b..0b4afea4 100644 --- a/tests/test_node_snapshot.c +++ b/tests/test_node_snapshot.c @@ -142,11 +142,47 @@ static void test_compact_addresses(struct UTUN_INSTANCE *inst) { sqlite3_close(db); inst->topo_sqlite_db = saved_db; } +/* Каждый размер буфера: длина, NUL, защитные байты, сохранённый префикс и маркер усечения. */ +static void test_node_format(struct TOPO_GROUP* group) { + char full[8192]; + int full_len = topo_node_format_all(group, full, sizeof(full)); + assert(full_len > 0 && (size_t)full_len == strlen(full) && !strstr(full, "[TRUNCATED]")); + assert(strstr(full, "=== BGP Nodes dump:")); + if (group->nodes->head) assert(strstr(full, " v4addr[0]:") && strstr(full, " path[0]:")); + const char marker[] = "\n[TRUNCATED]\n"; + debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_ERROR); + for (size_t capacity = 0; capacity <= (size_t)full_len + 2; capacity++) { + uint8_t* storage = u_malloc(capacity + 2); + assert(storage); memset(storage, 0xa5, capacity + 2); + char* buf = (char*)storage + 1; + int length = topo_node_format_all(group, buf, capacity); + assert(storage[0] == 0xa5 && storage[capacity + 1] == 0xa5); + if (!capacity) { assert(!length); u_free(storage); continue; } + size_t expected = (size_t)full_len < capacity ? (size_t)full_len : capacity - 1; + assert(length >= 0 && (size_t)length == expected); + assert(memchr(buf, '\0', capacity) == buf + length); + if (capacity > (size_t)full_len) assert(!memcmp(buf, full, full_len + 1)); + else { + size_t marker_len = expected < sizeof(marker) - 1 ? expected : sizeof(marker) - 1; + assert(!memcmp(buf, full, expected - marker_len)); + assert(!memcmp(buf + expected - marker_len, marker, marker_len)); + } + u_free(storage); + } + char empty[] = "old"; + assert(!topo_node_format_all(NULL, empty, sizeof(empty)) && !empty[0]); + assert(!topo_node_format_all(group, NULL, sizeof(empty))); + debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_DEBUG); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] node dump: nodes=%d capacities=0..%d, lengths, guards, NUL and truncation", + queue_entry_count(group->nodes), full_len + 2); +} + static void test_bgp(struct UTUN_INSTANCE* signer) { struct UTUN_INSTANCE receiver = { .ua = signer->ua, .topo_groups = signer->topo_groups }; struct TOPO_GROUP group = { .instance = &receiver, .group_id = 10, .group_type = TOPO_GROUP_TYPE_CHAT }; struct ETCP_CONN from = { .instance = &receiver, .peer_node_id = 7 }; group.nodes = queue_new(receiver.ua, 16, 0, 8, "snapshot_bgp"); + test_node_format(&group); struct TOPO_NODE* ni = record(signer, INT64_MAX - 7, "BGP", 1); from.peer_node_id = ni->node_id; from.links_up = 1; struct TOPO_GROUP_NODE nq = {0}; @@ -158,6 +194,7 @@ static void test_bgp(struct UTUN_INSTANCE* signer) { assert(peer && peer->last_timestamp == ni->timestamp && queue_entry_count(peer->paths) == 1); assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0); assert(queue_entry_count(peer->paths) == 1); + test_node_format(&group); struct TOPOMSG_NODEINFO_PKT* wire = (struct TOPOMSG_NODEINFO_PKT*)packet; wire->node.local_v4_subnets = 1; /* malformed CHAT не меняет уже принятую запись и маршрут */ assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) < 0);