From acd9dcd1a88f2148b5d67ceec1e29f7d0a668c4f Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 13:03:42 +0200 Subject: [PATCH] Cover empty NODEINFO outputs and malformed BGP preservation --- doc/node_snapshot.md | 8 ++++++++ tests/test_node_snapshot.c | 5 +++++ tests/test_topo_node_wire.c | 27 +++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/doc/node_snapshot.md b/doc/node_snapshot.md index 5ed661bb..c75ed994 100644 --- a/doc/node_snapshot.md +++ b/doc/node_snapshot.md @@ -6,6 +6,14 @@ заменяются вместе. Timestamp входит в подпись. Групповые пути и подсети остаются в `TOPO_GROUP_NODE` и не являются частью общей записи узла. +В NODEINFO секции подсетей присутствуют только при `TOPO_FLAG_SEND_SUBNETS`. +Без флага оба счётчика подсетей равны нулю; противоречащий этому заголовок отклоняется. +Parser проверяет точную длину пакета и проходит все присутствующие секции независимо +от запрошенных `out_*`: например, `out_subnets=NULL` отключает создание объектов подсетей, +но сохраняет проверку префиксов и правильное положение следующего hop-list. +Выходы выдаются только после полного успеха; при ошибке они обнулены, частичные объекты +освобождены. Подпись и допустимость пути проверяются после структурного разбора. + Получатель проверяет подпись до изменения реестра или маршрутов. Более свежая запись заменяет старую целиком, включая пустые списки адресов. Старые записи игнорируются; разные подписанные записи с одинаковым timestamp — конфликт. diff --git a/tests/test_node_snapshot.c b/tests/test_node_snapshot.c index 54af4728..2599532b 100644 --- a/tests/test_node_snapshot.c +++ b/tests/test_node_snapshot.c @@ -159,6 +159,11 @@ static void test_bgp(struct UTUN_INSTANCE* signer) { assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0); assert(queue_entry_count(peer->paths) == 1); 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); + assert(topo_node_find_by_id(&group, ni->node_id) == peer && queue_entry_count(peer->paths) == 1); + assert(peer->last_timestamp == ni->timestamp && topo_node_registry_find(signer->topo_groups, ni->node_id)->timestamp == ni->timestamp); + wire->node.local_v4_subnets = 0; wire->node.timestamp++; /* подделка не удаляет узел и ранее принятый маршрут */ assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) < 0); assert(topo_node_find_by_id(&group, ni->node_id) == peer && queue_entry_count(peer->paths) == 1); diff --git a/tests/test_topo_node_wire.c b/tests/test_topo_node_wire.c index a3a4f840..fe6636d9 100644 --- a/tests/test_topo_node_wire.c +++ b/tests/test_topo_node_wire.c @@ -216,6 +216,32 @@ static void test_encoder(struct wire_fixture *f) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] NODEINFO encoder emits only selected sections and checks exact buffer capacity"); } +static void test_empty_and_arguments(struct wire_fixture *f) { + memset(f->wire, 0, sizeof(f->wire)); + struct TOPOMSG_NODE *h = (struct TOPOMSG_NODE *)f->wire; + for (unsigned with_flag = 0; with_flag < 2; with_flag++) { + h->flags = with_flag ? TOPO_FLAG_SEND_SUBNETS : 0; + struct TOPO_NODE *node = NULL; struct TOPO_NODESUBNETS *subnets = (void *)(uintptr_t)1; + uint64_t *hops = (void *)(uintptr_t)1; uint8_t count = 99; uint16_t rtt = 99; + assert(!topo_node_deserialize(&f->group, f->wire, TOPOMSG_NODE_HDR_SIZE, &node, &subnets, &hops, &count, &rtt)); + assert(node && !node->node_name && !node->v4_addrs && !node->v6_addrs && !subnets && !hops && !count && !rtt); + result_free(f, node, subnets, hops); + } + struct TOPO_NODE *node = (void *)(uintptr_t)1; + assert(topo_node_deserialize(NULL, f->wire, TOPOMSG_NODE_HDR_SIZE, &node, NULL, NULL, NULL, NULL) < 0 && !node); + assert(topo_node_deserialize(&f->group, NULL, TOPOMSG_NODE_HDR_SIZE, &node, NULL, NULL, NULL, NULL) < 0 && !node); + assert(topo_node_deserialize(&f->group, f->wire, TOPOMSG_NODE_HDR_SIZE, NULL, NULL, NULL, NULL, NULL) < 0); + assert(topo_node_dyn_size(NULL) < 0); + fixture_packet(f, TOPO_FLAG_SEND_SUBNETS, 0); + struct TOPOMSG_SUBNET4 *v4 = (struct TOPOMSG_SUBNET4 *)(f->wire + f->subnet4_offset); + struct TOPOMSG_SUBNET6 *v6 = (struct TOPOMSG_SUBNET6 *)(f->wire + f->subnet6_offset); + v4[0].prefix_length = 0; v4[1].prefix_length = 32; + v6[0].prefix_length = 0; v6[1].prefix_length = 128; + assert(!topo_node_deserialize(&f->group, f->wire, f->length, &node, NULL, NULL, NULL, NULL)); + topo_node_destroy(&f->groups, node); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] NODEINFO empty sections, missing arguments and prefix boundaries"); +} + #ifdef __linux__ static void test_allocation_failures(struct wire_fixture *f) { fixture_packet(f, TOPO_FLAG_SEND_SUBNETS, 2); @@ -246,6 +272,7 @@ int main(void) { test_invalid_packets(&fixture); test_optional_outputs(&fixture); test_encoder(&fixture); + test_empty_and_arguments(&fixture); #ifdef __linux__ test_allocation_failures(&fixture); #endif