Prepare local topology replacements before retiring old state
This commit is contained in:
1 parent
42a58881ca
commit
1e0281d4d8
2 files changed
+156
-102
No files matched your search
+112
-102
@@ -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-сокетов; при изменении — подпись, БД, рассылка. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user