10 changed files with 440 additions and 95 deletions
@ -0,0 +1,25 @@ |
|||||||
|
# Подписанная запись узла |
||||||
|
|
||||||
|
`TOPO_NODE.timestamp` — версия всей подписанной записи, а не отдельного адреса. |
||||||
|
Узел-владелец назначает Unix microseconds, строго больше предыдущей сохранённой |
||||||
|
версии. Имя, тип, активность, адреса, метаданные сокетов, REALITY и подпись |
||||||
|
заменяются вместе. Timestamp входит в подпись. Групповые пути и подсети |
||||||
|
остаются в `TOPO_GROUP_NODE` и не являются частью общей записи узла. |
||||||
|
|
||||||
|
Получатель проверяет подпись до изменения реестра или маршрутов. Более свежая |
||||||
|
запись заменяет старую целиком, включая пустые списки адресов. Старые записи |
||||||
|
игнорируются; разные подписанные записи с одинаковым timestamp — конфликт. |
||||||
|
Повторное BGP-объявление может обновить путь, но не содержимое общей записи. |
||||||
|
8-битный `ver` сохраняется как диагностический счётчик; свежесть определяется |
||||||
|
только timestamp. |
||||||
|
|
||||||
|
Таблица `node_snapshots` хранит полную сериализованную запись без группового |
||||||
|
окружения. `node_addresses` остаётся проекцией для поиска и отображения, а не |
||||||
|
источником для восстановления подписанной записи. При наличии сохранённого |
||||||
|
snapshot загрузка использует его. Локальная публикация сохраняется до рассылки; |
||||||
|
ошибка сохранения не разрешает публикацию новой версии. |
||||||
|
|
||||||
|
Timestamp 0 обозначает bootstrap-сведения без подписи (конфиг, приглашение, |
||||||
|
начальные адреса). Они могут помочь установить первое соединение, но не |
||||||
|
заменяют принятую подписанную запись. По BGP принимаются подписанные записи |
||||||
|
с ненулевым timestamp. Формат NODEINFO изменён без обратной совместимости. |
||||||
@ -0,0 +1,150 @@ |
|||||||
|
#include <assert.h> |
||||||
|
#include <string.h> |
||||||
|
#include <openssl/evp.h> |
||||||
|
#include "utun_instance.h" |
||||||
|
#include "routing_layer/topo_group.h" |
||||||
|
#include "routing_layer/topo_node_sqlite.h" |
||||||
|
#include "transport_layer/secure_channel.h" |
||||||
|
#include "transport_layer/etcp.h" |
||||||
|
#include "../lib/mem.h" |
||||||
|
#include "../lib/memory_pool.h" |
||||||
|
#include "../lib/u_async.h" |
||||||
|
#include "../lib/debug_config.h" |
||||||
|
|
||||||
|
static void sign_record(struct UTUN_INSTANCE* inst, struct TOPO_NODE* ni) { |
||||||
|
uint8_t msg[TOPO_SIG_MSG_MAX_SIZE]; |
||||||
|
int len = topo_node_build_sig_msg(ni, msg, sizeof(msg)); |
||||||
|
assert(len > 0); |
||||||
|
assert(sc_ed25519_sign(inst->my_ed25519_privkey, msg, (size_t)len, ni->x25519_self_sig) == SC_OK); |
||||||
|
} |
||||||
|
|
||||||
|
static struct TOPO_NODE* record(struct UTUN_INSTANCE* inst, uint64_t timestamp, const char* name, int address) { |
||||||
|
struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni)); |
||||||
|
assert(ni); |
||||||
|
ni->node_id = inst->node_id; |
||||||
|
ni->timestamp = timestamp; |
||||||
|
ni->node_name = u_strdup(name); |
||||||
|
memcpy(ni->public_key, inst->my_keys.public_key, 32); |
||||||
|
memcpy(ni->ed25519_public_key, inst->my_ed25519_pubkey, 32); |
||||||
|
if (address) { |
||||||
|
ni->v4_addrs = memory_pool_alloc(inst->topo_groups->v4_addr_pool); |
||||||
|
assert(ni->v4_addrs); |
||||||
|
*ni->v4_addrs = (struct TOPO_ADDR4){ .addr = {127,0,0,1}, .port = 9000, .protocol = TOPO_PROTO_UDP }; |
||||||
|
} |
||||||
|
sign_record(inst, ni); |
||||||
|
return ni; |
||||||
|
} |
||||||
|
|
||||||
|
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"); |
||||||
|
struct TOPO_NODE* ni = record(signer, INT64_MAX - 7, "BGP", 1); |
||||||
|
struct TOPO_GROUP_NODE nq = {0}; |
||||||
|
uint8_t packet[4096] = {0}; |
||||||
|
int len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + 2, sizeof(packet) - 2, 0); |
||||||
|
assert(len > 0); |
||||||
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) == 0); |
||||||
|
struct TOPO_GROUP_NODE* peer = topo_node_find_by_id(&group, ni->node_id); |
||||||
|
assert(peer && peer->last_timestamp == ni->timestamp && queue_entry_count(peer->paths) == 1); |
||||||
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) == 0); |
||||||
|
assert(queue_entry_count(peer->paths) == 1); |
||||||
|
struct TOPOMSG_NODEINFO_PKT* wire = (struct TOPOMSG_NODEINFO_PKT*)packet; |
||||||
|
wire->node.timestamp++; /* подделка не удаляет узел и ранее принятый маршрут */ |
||||||
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) < 0); |
||||||
|
assert(topo_node_find_by_id(&group, ni->node_id) == peer && queue_entry_count(peer->paths) == 1); |
||||||
|
wire->node.timestamp--; |
||||||
|
ni->timestamp--; |
||||||
|
sign_record(signer, ni); |
||||||
|
len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + 2, sizeof(packet) - 2, 0); |
||||||
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) == 0); |
||||||
|
assert(peer->last_timestamp == INT64_MAX - 7 && queue_entry_count(peer->paths) == 1); |
||||||
|
topo_group_remove_path(peer, &from); |
||||||
|
topo_nodeq_remove_node(&group, peer); |
||||||
|
topo_node_destroy(signer->topo_groups, ni); |
||||||
|
queue_free(group.nodes); |
||||||
|
} |
||||||
|
|
||||||
|
int main(void) { |
||||||
|
debug_config_init(); |
||||||
|
debug_set_level(DEBUG_LEVEL_DEBUG); |
||||||
|
struct UTUN_INSTANCE inst = {0}; |
||||||
|
struct TOPO_GROUPS groups = { .instance = &inst }; |
||||||
|
inst.topo_groups = &groups; |
||||||
|
inst.ua = uasync_create(); |
||||||
|
assert(inst.ua); |
||||||
|
groups.node_registry = queue_new(inst.ua, 16, 0, 8, "snapshot_test"); |
||||||
|
groups.v4_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR4), "snapshot_v4"); |
||||||
|
assert(sc_generate_keypair(&inst.my_keys) == SC_OK); |
||||||
|
memcpy(inst.my_ed25519_privkey, inst.my_keys.private_key, 32); |
||||||
|
EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, inst.my_ed25519_privkey, 32); |
||||||
|
size_t key_len = 32; |
||||||
|
assert(key && EVP_PKEY_get_raw_public_key(key, inst.my_ed25519_pubkey, &key_len) == 1); |
||||||
|
EVP_PKEY_free(key); |
||||||
|
inst.node_id = sc_derive_node_id_from_pubkey(inst.my_keys.public_key); |
||||||
|
assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK); |
||||||
|
assert(topo_node_sqlite_init(inst.topo_sqlite_db) == 0); |
||||||
|
|
||||||
|
struct TOPO_NODE* first = record(&inst, 100, "old", 1); |
||||||
|
assert(topo_node_verify(first) == 0); |
||||||
|
first->timestamp++; |
||||||
|
assert(topo_node_verify(first) < 0); /* timestamp защищён подписью */ |
||||||
|
first->timestamp--; |
||||||
|
assert(topo_node_registry_store(&groups, first) == first); |
||||||
|
struct TOPO_NODE* stale = record(&inst, 99, "stale", 0); |
||||||
|
assert(topo_node_registry_store(&groups, stale) == first); |
||||||
|
topo_node_registry_unref(&groups, inst.node_id); |
||||||
|
assert(first->timestamp == 100 && first->v4_addrs && !strcmp(first->node_name, "old")); |
||||||
|
|
||||||
|
struct TOPO_NODE* conflict = record(&inst, 100, "conflict", 0); |
||||||
|
assert(topo_node_registry_store(&groups, conflict) == first); |
||||||
|
topo_node_registry_unref(&groups, inst.node_id); |
||||||
|
assert(first->v4_addrs && !strcmp(first->node_name, "old")); |
||||||
|
|
||||||
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); |
||||||
|
struct TOPO_NODE* loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id); |
||||||
|
assert(loaded && loaded->timestamp == 100 && loaded->v4_addrs && topo_node_verify(loaded) == 0); |
||||||
|
topo_node_destroy(&groups, loaded); |
||||||
|
|
||||||
|
struct TOPO_NODE* fresh = record(&inst, 101, "new", 0); |
||||||
|
assert(topo_node_registry_store(&groups, fresh) == first); |
||||||
|
topo_node_registry_unref(&groups, inst.node_id); |
||||||
|
assert(first->timestamp == 101 && !first->v4_addrs && !strcmp(first->node_name, "new")); |
||||||
|
assert(topo_node_verify(first) == 0); /* удаление адресов не оставляет частей старой записи */ |
||||||
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); |
||||||
|
loaded = record(&inst, 100, "old", 1); |
||||||
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, loaded) == 0); |
||||||
|
topo_node_destroy(&groups, loaded); |
||||||
|
loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id); |
||||||
|
assert(loaded && loaded->timestamp == 101 && !loaded->v4_addrs && topo_node_verify(loaded) == 0); |
||||||
|
topo_node_destroy(&groups, loaded); |
||||||
|
|
||||||
|
/* Сохранённая версия из будущего: локальные часы/перезапуск не откатывают timestamp. */ |
||||||
|
topo_node_registry_unref(&groups, inst.node_id); |
||||||
|
first = topo_node_registry_store(&groups, record(&inst, 99, "stale after restart", 1)); |
||||||
|
assert(first && first->timestamp == 101 && !first->v4_addrs && topo_node_verify(first) == 0); |
||||||
|
first->timestamp = INT64_MAX - 10; |
||||||
|
sign_record(&inst, first); |
||||||
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); |
||||||
|
first->timestamp = 0; |
||||||
|
assert(topo_node_sign_self(&inst, first) == 0); |
||||||
|
assert(first->timestamp == INT64_MAX - 9 && topo_node_verify(first) == 0); |
||||||
|
assert(topo_node_sign_self(&inst, first) == 0); |
||||||
|
assert(first->timestamp == INT64_MAX - 8 && topo_node_verify(first) == 0); |
||||||
|
test_bgp(&inst); |
||||||
|
uint64_t before = first->timestamp; |
||||||
|
assert(sqlite3_exec(inst.topo_sqlite_db, |
||||||
|
"CREATE TRIGGER fail_snapshot BEFORE UPDATE ON node_snapshots BEGIN SELECT RAISE(ABORT,'test write failure'); END", |
||||||
|
NULL, NULL, NULL) == SQLITE_OK); |
||||||
|
assert(topo_node_sign_self(&inst, first) < 0); |
||||||
|
assert(first->timestamp == before && topo_node_verify(first) == 0); |
||||||
|
|
||||||
|
topo_node_registry_unref(&groups, inst.node_id); |
||||||
|
assert(!groups.node_registry->head); |
||||||
|
queue_free(groups.node_registry); |
||||||
|
memory_pool_destroy(groups.v4_addr_pool); |
||||||
|
sqlite3_close(inst.topo_sqlite_db); |
||||||
|
uasync_destroy(inst.ua, 0); |
||||||
|
return 0; |
||||||
|
} |
||||||
Loading…
Reference in new issue