diff --git a/src/routing_layer/topo_node.c b/src/routing_layer/topo_node.c index d4765563..18016f8b 100644 --- a/src/routing_layer/topo_node.c +++ b/src/routing_layer/topo_node.c @@ -1107,114 +1107,124 @@ int topo_node_init_self(struct UTUN_INSTANCE* instance) { return 0; } -/* Пересоздаёт local_node группы при изменении имени/подсетей/ключей, инкрементируя версию. */ +/* Готовит 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) - || (group->local_node->phone_status != call_phone_status(instance)) - || (memcmp(oni->ed25519_public_key, group->ed25519_public_key, SC_PUBKEY_SIZE) != 0); - } + if (!instance || !group || group->instance != instance || !instance->config || !instance->topo_groups) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: invalid instance/group/config"); return -1; } - 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; - lq->phone_status = call_phone_status(instance); - 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; + struct TOPO_GROUPS* groups = instance->topo_groups; + struct TOPO_GROUP_NODE* old = group->local_node; + struct TOPO_GROUP_NODE* lq = u_calloc(1, sizeof(*lq)); + if (!lq) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: allocation failed"); return -1; } + struct TOPO_NODE* ni = NULL; + char* name = NULL; + int new_identity = 0, retained = 0, routes_changed = 0, vc = 0, vc6 = 0; + lq->node_id = instance->node_id; lq->radio = group->radio_active; lq->phone_status = call_phone_status(instance); + if (group->group_type != TOPO_GROUP_TYPE_CHAT) { + struct TOPO_SUBNET4** tail4 = NULL; struct TOPO_SUBNET6** tail6 = NULL; + for (struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets; s; s = s->next) { + if ((s->ip.family != AF_INET && s->ip.family != AF_INET6) || + s->netmask > (s->ip.family == AF_INET ? 32 : 128)) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: invalid subnet family=%d prefix=%u", s->ip.family, s->netmask); + goto fail; + } + if (!lq->subnets) { + lq->subnets = u_calloc(1, sizeof(*lq->subnets)); + if (!lq->subnets) goto allocation_failed; + tail4 = &lq->subnets->v4_subnets; tail6 = &lq->subnets->v6_subnets; + } + if (s->ip.family == AF_INET) { + struct TOPO_SUBNET4* sub = memory_pool_alloc(groups->v4_subnet_pool); + if (!sub) goto allocation_failed; + *sub = (struct TOPO_SUBNET4){.prefix_length = s->netmask}; memcpy(sub->addr, &s->ip.addr.v4, 4); + *tail4 = sub; tail4 = &sub->next; vc++; + } else { + struct TOPO_SUBNET6* sub = memory_pool_alloc(groups->v6_subnet_pool); + if (!sub) goto allocation_failed; + *sub = (struct TOPO_SUBNET6){.prefix_length = s->netmask}; memcpy(sub->addr, &s->ip.addr.v6, 16); + *tail6 = sub; tail6 = &sub->next; vc6++; } - 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; + if (vc > UINT8_MAX || vc6 > UINT8_MAX) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: subnet counters exceed wire limit v4=%d v6=%d", vc, vc6); goto fail; + } } + ni = topo_node_registry_find(groups, instance->node_id); + int changed = !old || !ni || strcmp(ni->node_name ? ni->node_name : "", instance->name) || + memcmp(ni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE) || + memcmp(ni->ed25519_public_key, instance->my_ed25519_pubkey, SC_PUBKEY_SIZE) || + ni->client_type != instance->client_type || ni->client_activity != instance->client_activity || + old->radio != lq->radio || old->phone_status != lq->phone_status; + if (!changed) { + struct TOPO_SUBNET4* a4 = old->subnets ? old->subnets->v4_subnets : NULL; + struct TOPO_SUBNET4* b4 = lq->subnets ? lq->subnets->v4_subnets : NULL; + while (a4 && b4 && a4->prefix_length == b4->prefix_length && !memcmp(a4->addr, b4->addr, 4)) { a4 = a4->next; b4 = b4->next; } + struct TOPO_SUBNET6* a6 = old->subnets ? old->subnets->v6_subnets : NULL; + struct TOPO_SUBNET6* b6 = lq->subnets ? lq->subnets->v6_subnets : NULL; + while (a6 && b6 && a6->prefix_length == b6->prefix_length && !memcmp(a6->addr, b6->addr, 16)) { a6 = a6->next; b6 = b6->next; } + changed = a4 || b4 || a6 || b6; + } + if (!changed) { + old->last_ver = ni->ver; old->last_timestamp = ni->timestamp; + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "local node unchanged: group=%016llx ver=%u", (unsigned long long)group->group_id, ni->ver); + goto unchanged; + } + name = u_strdup(instance->name); + if (!name) goto allocation_failed; + if (!ni) { + ni = u_calloc(1, sizeof(*ni)); new_identity = 1; + if (!ni) goto allocation_failed; + } else { topo_node_registry_ref(groups, instance->node_id); retained = 1; } + struct TOPO_NODE candidate = *ni; + candidate.node_id = instance->node_id; candidate.ver = (ni->ver % 255) + 1; + candidate.node_name = name; candidate.client_type = instance->client_type; candidate.client_activity = instance->client_activity; + memcpy(candidate.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE); + memcpy(candidate.ed25519_public_key, instance->my_ed25519_pubkey, SC_PUBKEY_SIZE); + if (group->group_type != TOPO_GROUP_TYPE_CHAT && instance->rt) { + routes_changed = 1; + route_delete(instance->rt, old); + if (vc && !route_insert(instance->rt, lq)) { + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "local node update: route conflict/allocation group=%016llx", + (unsigned long long)group->group_id); goto fail; + } + } + if (topo_node_sign_self(instance, &candidate) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: cannot publish group=%016llx", (unsigned long long)group->group_id); goto fail; + } + char* old_name = ni->node_name; + *ni = candidate; name = NULL; u_free(old_name); + if (new_identity) { + struct TOPO_NODE* stored = topo_node_registry_store(groups, ni); + if (!stored) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: cannot register identity"); goto fail; } + ni = stored; new_identity = 0; retained = 1; + } + lq->last_ver = ni->ver; lq->last_timestamp = ni->timestamp; + if (old) { topo_nodeq_clear_paths(old); topo_nodeq_free_group_fields(groups, old); u_free(old); } + group->local_node = lq; + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "local node published: group=%016llx v4=%d v6=%d ver=%u timestamp=%llu", + (unsigned long long)group->group_id, vc, vc6, ni->ver, (unsigned long long)ni->timestamp); return vc; +allocation_failed: + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: preparation allocation failed group=%016llx", + (unsigned long long)group->group_id); +fail: + if (routes_changed) { + route_delete(instance->rt, lq); + if (old && old->subnets && old->subnets->v4_subnets && !route_insert(instance->rt, old)) + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "local node update: previous routes restoration failed group=%016llx", + (unsigned long long)group->group_id); + } + if (new_identity) topo_node_destroy(groups, ni); + if (retained) topo_node_registry_unref(groups, instance->node_id); + u_free(name); +unchanged: + if (lq->subnets) { + free_v4_sub_list(groups->v4_subnet_pool, lq->subnets->v4_subnets); + free_v6_sub_list(groups->v6_subnet_pool, lq->subnets->v6_subnets); + u_free(lq->subnets); + } + u_free(lq); + return changed ? -1 : vc; } /* Пересобирает списки адресов своего узла из ETCP-сокетов; при изменении — подпись, БД, рассылка. */ diff --git a/tests/test_node_snapshot.c b/tests/test_node_snapshot.c index 20c4496f..c6d3abda 100644 --- a/tests/test_node_snapshot.c +++ b/tests/test_node_snapshot.c @@ -4,6 +4,7 @@ #include "utun_instance.h" #include "routing_layer/topo_group.h" #include "routing_layer/topo_node_sqlite.h" +#include "routing_layer/route_lib.h" #include "transport_layer/secure_channel.h" #include "transport_layer/etcp.h" #include "config_parser.h" @@ -292,6 +293,44 @@ static void test_bgp(struct UTUN_INSTANCE* signer) { queue_free(group.nodes); } +/* Замена подсети при том же количестве и откат ошибок сохраняют согласованные local_node/route table. */ +static void test_local_group_update(struct UTUN_INSTANCE* inst) { + sqlite3* saved_db = inst->topo_sqlite_db; + assert(sqlite3_open(":memory:", &inst->topo_sqlite_db) == SQLITE_OK && !topo_node_sqlite_init(inst->topo_sqlite_db)); + struct CFG_ROUTE_ENTRY subnet = {.ip.family = AF_INET, .netmask = 24}; + subnet.ip.addr.v4.s_addr = htonl(0x0a010000); + struct utun_config config = {.my_subnets = &subnet}; + inst->config = &config; inst->rt = route_table_create(inst->node_id); assert(inst->rt); + struct TOPO_GROUP group = {.instance = inst, .group_type = TOPO_GROUP_TYPE_UTUN, .group_id = TOPO_GROUP_UTUN}; + memcpy(group.ed25519_public_key, inst->my_ed25519_pubkey, 32); + assert(topo_group_update_my_nodeinfo(inst, &group) == 1 && route_lookup(inst->rt, 0x0a010001)); + subnet.ip.addr.v4.s_addr = htonl(0x0a020000); + assert(topo_group_update_my_nodeinfo(inst, &group) == 1 && route_lookup(inst->rt, 0x0a020001)); + assert(!route_lookup(inst->rt, 0x0a010001)); + struct TOPO_GROUP_NODE* before = group.local_node; + struct TOPO_NODE* identity = topo_node_registry_find(inst->topo_groups, inst->node_id); + uint64_t timestamp = identity->timestamp; uint32_t refs = identity->group_ref_count; + assert(sqlite3_exec(inst->topo_sqlite_db, "CREATE TRIGGER reject_local BEFORE UPDATE ON node_snapshots " + "BEGIN SELECT RAISE(ABORT,'test local identity rejection'); END", NULL, NULL, NULL) == SQLITE_OK); + subnet.ip.addr.v4.s_addr = htonl(0x0a030000); + assert(topo_group_update_my_nodeinfo(inst, &group) < 0 && group.local_node == before); + assert(route_lookup(inst->rt, 0x0a020001) && !route_lookup(inst->rt, 0x0a030001)); + assert(identity->timestamp == timestamp && identity->group_ref_count == refs && !topo_node_verify(identity)); + assert(sqlite3_exec(inst->topo_sqlite_db, "DROP TRIGGER reject_local", NULL, NULL, NULL) == SQLITE_OK); + struct TOPO_SUBNET4 remote_sub = {.addr = {10, 3, 0, 0}, .prefix_length = 24}; + struct TOPO_NODESUBNETS remote_subnets = {.v4_subnets = &remote_sub}; + struct TOPO_GROUP_NODE remote = {.node_id = 999, .subnets = &remote_subnets}; + assert(route_insert(inst->rt, &remote)); + assert(topo_group_update_my_nodeinfo(inst, &group) < 0 && group.local_node == before); + assert(route_lookup(inst->rt, 0x0a020001)->v_node_info == before && route_lookup(inst->rt, 0x0a030001)->v_node_info == &remote); + assert(identity->timestamp == timestamp && identity->group_ref_count == refs && !topo_node_verify(identity)); + route_table_destroy(inst->rt); inst->rt = NULL; + topo_nodeq_free_group_fields(inst->topo_groups, group.local_node); u_free(group.local_node); + assert(!inst->topo_groups->node_registry->head); + sqlite3_close(inst->topo_sqlite_db); inst->topo_sqlite_db = saved_db; inst->config = NULL; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] local group: same-count subnet replacement, signature failure and route conflict rollback"); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG); @@ -305,6 +344,8 @@ int main(void) { groups.v4_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA4), "snapshot_meta4"); groups.v6_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA6), "snapshot_meta6"); groups.v6_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR6), "snapshot_v6"); + groups.v4_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET4), "snapshot_subnet4"); + groups.v6_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET6), "snapshot_subnet6"); assert(groups.v4_addr_pool && groups.v4_sock_meta_pool && groups.v6_sock_meta_pool && groups.v6_addr_pool); assert(sc_generate_keypair(&inst.my_keys) == SC_OK); memcpy(inst.my_ed25519_privkey, inst.my_keys.private_key, 32); @@ -317,6 +358,7 @@ int main(void) { assert(topo_node_sqlite_init(inst.topo_sqlite_db) == 0); test_verified_write(); test_compact_addresses(&inst); + test_local_group_update(&inst); struct TOPO_NODE* first = record(&inst, 100, "old", 1); assert(topo_node_verify(first) == 0); @@ -379,6 +421,8 @@ int main(void) { memory_pool_destroy(groups.v4_sock_meta_pool); memory_pool_destroy(groups.v6_sock_meta_pool); memory_pool_destroy(groups.v6_addr_pool); + memory_pool_destroy(groups.v4_subnet_pool); + memory_pool_destroy(groups.v6_subnet_pool); sqlite3_close(inst.topo_sqlite_db); uasync_destroy(inst.ua, 0); return 0;